diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index b3c7d0a2..00000000 --- a/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[run] -branch = True -source = yabgp -omit = yabgp/tests/* diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..f1d8cece --- /dev/null +++ b/.dockerignore @@ -0,0 +1,66 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd +*.so + +# Virtual environments +.venv/ +venv/ +env/ +mac-venv/ +mac-venv-py313/ + +# uv cache +.uv/ + +# Distribution / build +build/ +dist/ +*.egg-info/ +*.egg + +# Testing / coverage +.tox/ +htmlcov/ +.coverage +.coverage.* +.pytest_cache/ +.ruff_cache/ +.pyright/ +coverage.xml + +# Version control +.git/ +.gitignore + +# IDE / editor +.idea/ +.vscode/ +*.DS_Store + +# Documentation build output +doc/build/ + +# Secrets / local config +etc/yabgp/yabgp.ini + +# CI +.github/ + +# Cursor AI +.claude/ +.cursor/ + +# Development-only directories (not needed in production image) +tools/ +doc/ +example/ +bin/ +yabgp/tests/ + +# Documentation files +*.rst +*.md +.mailmap diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6948a596..58bb5e41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -# This workflow will install Python dependencies, run tests with a variety of Python versions +# This workflow installs Python dependencies and runs tests on Python 3.13 # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: CI @@ -17,32 +17,33 @@ jobs: strategy: fail-fast: false matrix: - # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources - os: [ ubuntu-20.04, macos-latest, windows-latest ] - python-version: [ "3.6", "3.x" ] - exclude: - - python-version: "3.x" + os: [ ubuntu-latest, macos-latest, windows-latest ] + python-version: [ "3.13" ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Set up uv + uses: astral-sh/setup-uv@v5 + - name: Display Python version run: python -c "import sys; print(sys.version)" - - name: Install tox and other dependencies + - name: Sync dependencies run: | - python -m pip install --upgrade pip setuptools wheel - python -m pip install tox - python -m pip install -r requirements.txt - python -m pip install -r test-requirements.txt + uv --version + uv sync --locked --group test --group dev + + - name: Run tests + run: uv run pytest yabgp/tests/unit/ --tb=short -q - - name: Run tox - run: tox -e pep8 -c tox.ini + - name: Lint + run: uv run ruff check yabgp/ - - name: Run unittest - run: python run_test.py + - name: Type check + run: uv run pyright -p pyrightconfig.json diff --git a/.gitignore b/.gitignore index e3690a84..fcfbc595 100644 --- a/.gitignore +++ b/.gitignore @@ -36,9 +36,13 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +.venv/ +.ruff_cache/ +.pyright/ .coverage .coverage.* .cache +.pytest_cache/ nosetests.xml coverage.xml *,cover @@ -70,3 +74,5 @@ covhtml/ doc/build *.DS_Store .vscode/settings.json +mac-venv/ +.claude/ diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index c1f9c13f..00000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_LOG_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./yabgp/tests/unit} $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 3c31cc7c..0f6f02be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,25 @@ -FROM python:2.7.14-alpine +FROM python:3.13-alpine LABEL maintainer="Peng Xiao " +# Install uv binary from official image +COPY --from=ghcr.io/astral-sh/uv:0.9.15 /uv /bin/uv + RUN apk add --no-cache gcc musl-dev g++ -ADD . /yabgp +COPY . /yabgp WORKDIR /yabgp -RUN pip install -r requirements.txt && python setup.py install +ENV UV_LINK_MODE=copy + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --locked --no-dev EXPOSE 8801 VOLUME ["~/data"] -ENTRYPOINT ["/usr/local/bin/yabgpd"] +ENTRYPOINT ["/yabgp/.venv/bin/yabgpd"] CMD [] diff --git a/HACKING.rst b/HACKING.rst index aed265ce..a51d62f8 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -15,19 +15,29 @@ Step 3: Read on Running Tests ------------- -Run tox +Using uv + pytest (recommended): .. code:: bash - + $ cd yabgp - $ tox + $ uv sync --group test --group dev + $ uv run pytest yabgp/tests/unit/ -v + +Running Lint / Type Check +------------------------- + +.. code:: bash + + $ cd yabgp + $ uv run ruff check yabgp/ --exclude yabgp/tests + $ uv run pyright + Building Docs ------------- -Run tox - .. code:: bash - + $ cd yabgp - $ tox + $ uv sync --group docs + $ uv run sphinx-build -b html doc/source doc/build diff --git a/README-zh.rst b/README-zh.rst index 2a94d6c9..62ebe464 100644 --- a/README-zh.rst +++ b/README-zh.rst @@ -12,21 +12,22 @@ YABGP是另一种BGP协议的Python实现。它可以和各种路由器(包括 我们严格遵循RFCs文档的约定开发此项目。 -此软件可应用于Linux/Unix,Mac OS和windows系统。 +此软件可应用于Linux/Unix、Mac OS和Windows系统。需要 Python >= 3.13。 +TCP MD5认证仅支持Linux。 功能 ~~~~~~~~ - 它可以通过IPv4地址以主动模式(作为TCP客户端)建立BGP会话连接。 -- 支持TCP的MD5认证(只有IPv4并且不支持windows系统) +- 支持TCP的MD5认证(只有IPv4) - BGP capabilities支持:4字节的ASN,Route Refresh(Cisco Route Refresh),添加发送/接收路径; - 地址族支持: - IPv4/IPv6 Unicast - + - IPv4/IPv6 Labeled Unicast - IPv4 Flowspec(有限支持) @@ -36,7 +37,7 @@ YABGP是另一种BGP协议的Python实现。它可以和各种路由器(包括 - IPv4/IPv6 MPLSVPN - EVPN (部分支持) - + - 解析所有BGP messages为json格式并写入本地文件(可配置); - 支持通过基本的RESTFUL API获取对等体运行信息或者发送BGP messages。 @@ -44,32 +45,29 @@ YABGP是另一种BGP协议的Python实现。它可以和各种路由器(包括 快速开始 ~~~~~~~~~~~ -我们推荐在python的虚拟环境中运行``yabgp``,可以通过源码或者pip工具安装 - -源码安装: +**使用 uv(推荐):** .. code:: bash - $ virtualenv yabgp-virl - $ source yabgp-virl/bin/activate $ git clone https://github.com/smartbgp/yabgp $ cd yabgp - $ pip install -r requirements.txt - $ cd bin - $ python yabgpd -h + $ uv sync + $ uv run yabgpd -h -pip安装: +**使用 pip:** .. code:: bash - $ virtualenv yabgp-virl - $ source yabgp-virl/bin/activate $ pip install yabgp - $ which yabgpd - /home/yabgp/yabgp-virl/bin/yabgpd $ yabgpd -h -例如: +**使用 Docker:** + +.. code:: bash + + $ docker run -it smartbgp/yabgp:latest --bgp-afi_safi=ipv4 --bgp-local_as=65022 --bgp-remote_addr=10.75.44.219 --bgp-remote_as=65022 + +**例如:** .. code:: bash @@ -90,8 +88,6 @@ pip安装: 支持 ~~~~~~~ -加入Slack,欢迎问题与建议,我们一起讨论。http://smartbgp.slack.com/ - 可以发送email到xiaoquwl@gmail.com,或者在GitHub上提issue。 贡献 diff --git a/README.rst b/README.rst index be0c9867..68ee0c3b 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,11 @@ 简体中文_ - + .. _简体中文: README-zh.rst YABGP ===== -|Docker| |Version| |License| |Build Status| |Code Health| |Documentation Status| |Test Coverage| |Downloads| +|License| |Version| |CI| |Documentation Status| What is yabgp? @@ -17,11 +17,12 @@ simulators like GNS3) and receive/parse BGP messages for future analysis. Support sending BGP messages(route refresh/update) to the peer through RESTful API. YABGP can't send any BGP update messages -by itself, it's just a agent, so there can be many agents and they can be controlled by a contoller. +by itself, it's just a agent, so there can be many agents and they can be controlled by a controller. We write it in strict accordance with the specifications of RFCs. -This software can be used on Linux/Unix, Mac OS and Windows systems. +This software can be used on Linux/Unix, Mac OS and Windows systems. Python >= 3.13 required. +TCP MD5 authentication is only supported on Linux. Features ~~~~~~~~ @@ -29,8 +30,7 @@ Features - It can establish BGP session based on IPv4 address (TCP Layer) in active mode(as TCP client); -- Support TCP MD5 authentication(IPv4 and does not support Windows - now); +- Support TCP MD5 authentication(IPv4 only); - BGP capabilities support: 4 Bytes ASN, Route Refresh(Cisco Route Refresh), Add Path send/receive; @@ -55,45 +55,33 @@ Features Quick Start ~~~~~~~~~~~ -We recommend run ``yabgp`` through python virtual-env from source -code or pip install - -Use yabgp from source code: +**Using uv (recommended):** .. code:: bash - $ virtualenv yabgp-virl - $ source yabgp-virl/bin/activate $ git clone https://github.com/smartbgp/yabgp $ cd yabgp - $ pip install -r requirements.txt - $ cd bin - $ python yabgpd -h + $ uv sync + $ uv run yabgpd -h -Use pip install +**Using pip:** .. code:: bash - $ virtualenv yabgp-virl - $ source yabgp-virl/bin/activate $ pip install yabgp - $ which yabgpd - /home/yabgp/yabgp-virl/bin/yabgpd $ yabgpd -h -For example: +**Using Docker:** .. code:: bash - $ yabgpd --bgp-local_addr=1.1.1.1 --bgp-local_as=65001 --bgp-remote_addr=1.1.1.2 --bgp-remote_as=65001 --bgp-afi_safi=ipv4 + $ docker run -it smartbgp/yabgp:latest --bgp-afi_safi=ipv4 --bgp-local_as=65022 --bgp-remote_addr=10.75.44.219 --bgp-remote_as=65022 -Use Docker container +**Example:** .. code:: bash - $ docker run -it smartbgp/yabgp:latest --bgp-afi_safi=ipv4 --bgp-local_as=65022 --bgp-remote_addr=10.75.44.219 --bgp-remote_as=65022 - -More docker image tags, please reference https://hub.docker.com/r/smartbgp/yabgp/tags/ + $ yabgpd --bgp-local_addr=1.1.1.1 --bgp-local_as=65001 --bgp-remote_addr=1.1.1.2 --bgp-remote_as=65001 --bgp-afi_safi=ipv4 Documentation ~~~~~~~~~~~~~ @@ -110,8 +98,6 @@ A BGP update generator based on YaBGP https://github.com/trungdtbk/bgp-update-ge Support ~~~~~~~ -Please join our Slack_ for questions, discussion, suggestions, etc - Send email to xiaoquwl@gmail.com, or use GitHub issue system. @@ -131,27 +117,14 @@ https://github.com/wikimedia/PyBal/blob/master/pybal/bgp.py, and message parsing, we reference from https://github.com/Exa-Networks/exabgp -.. |License| image:: https://img.shields.io/hexpm/l/plug.svg +.. |License| image:: https://img.shields.io/badge/license-Apache%202.0-blue.svg :target: https://github.com/smartbgp/yabgp/blob/master/LICENSE -.. |Build Status| image:: https://travis-ci.org/smartbgp/yabgp.svg?branch=master - :target: https://travis-ci.org/smartbgp/yabgp -.. |Code Health| image:: https://landscape.io/github/smartbgp/yabgp/master/landscape.svg?style=flat - :target: https://landscape.io/github/smartbgp/yabgp/master +.. |CI| image:: https://github.com/smartbgp/yabgp/actions/workflows/ci.yml/badge.svg?branch=master + :target: https://github.com/smartbgp/yabgp/actions/workflows/ci.yml .. |Documentation Status| image:: https://readthedocs.org/projects/yabgp/badge/?version=latest :target: https://readthedocs.org/projects/yabgp/?badge=latest -.. |Test Coverage| image:: https://coveralls.io/repos/smartbgp/yabgp/badge.svg?branch=master - :target: https://coveralls.io/r/smartbgp/yabgp - -.. |Version| image:: https://img.shields.io/pypi/v/yabgp.svg? - :target: http://badge.fury.io/py/yabgp - -.. |Downloads| image:: https://img.shields.io/pypi/dm/yabgp.svg? - :target: https://pypi.python.org/pypi/yabgp - -.. |Docker| image:: https://img.shields.io/docker/automated/jrottenberg/ffmpeg.svg?style=plastic - :target: https://hub.docker.com/r/smartbgp/yabgp/ - -.. _Slack: https://join.slack.com/t/smartbgp/shared_invite/enQtNzQwNjgzNTA5NjA3LTY3YzFkODMzYWFjODI1ZmE3NDRkMTQwYTY0MWZiMmE3M2NiMzM0ZTI3NjNjY2RkNDAzMmFkZWJkOTE2M2VjOWU +.. |Version| image:: https://img.shields.io/pypi/v/yabgp.svg + :target: https://pypi.org/project/yabgp/ diff --git a/doc/requirements.txt b/doc/requirements.txt deleted file mode 100644 index 57d9ab01..00000000 --- a/doc/requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ -sphinx -sphinx_rtd_theme -pbr==2.0.0 -oslo.config==2.1.0 -Twisted==20.3.0 -Flask==1.0 -Flask-HTTPAuth==2.5.0 -netaddr>=0.7.12 -future>=0.16.0 -py-radix==0.10.0 \ No newline at end of file diff --git a/doc/source/extension.rst b/doc/source/extension.rst index 03b40831..56639391 100644 --- a/doc/source/extension.rst +++ b/doc/source/extension.rst @@ -15,7 +15,6 @@ please reference the ``DefaultHandler``. #!/usr/bin/python # -*- coding: utf-8 -*- - from __future__ import print_function import sys from yabgp.agent import prepare_service @@ -72,7 +71,7 @@ How to run it? very simple! let's call this file as ``my_bgpd.py``, and you can .. note:: - Please make sure you have install yabgp for requirements, you can do that through ``pip install yabgp`` + Please make sure you have installed yabgp: ``pip install yabgp`` or ``uv sync`` .. code:: bash diff --git a/doc/source/feature.rst b/doc/source/feature.rst index f0654f64..83c98ca7 100644 --- a/doc/source/feature.rst +++ b/doc/source/feature.rst @@ -4,8 +4,7 @@ Features - It can establish BGP session based on IPv4 address (TCP Layer) in active mode(as TCP client); -- Support TCP MD5 authentication(IPv4 and does not support Windows - now); +- Support TCP MD5 authentication(IPv4 only, Linux only); - BGP capabilities support: 4 Bytes ASN, Route Refresh(Cisco Route Refresh), Add Path send/receive; @@ -25,7 +24,8 @@ Features - Support basic RESTFUL API for getting running information and sending BGP messages. -- Platform support: Linux/Unix(recommended), Mac OS and Windows. +- Platform support: Linux/Unix (recommended), Mac OS, Windows. + TCP MD5 authentication is only supported on Linux. .. note:: diff --git a/doc/source/index.rst b/doc/source/index.rst index 78fd4859..8429b725 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -12,7 +12,7 @@ simulators like GNS3) and receive/parse BGP messages for future analysis. Support sending BGP messages(route refresh/update) to the peer through RESTful API. YABGP can't send any BGP update messages -by itself, it's just a agent, so there can be many agents and they can be controlled by a contoller. +by itself, it's just an agent, so there can be many agents and they can be controlled by a controller. Table of Contents diff --git a/doc/source/install.rst b/doc/source/install.rst index cd5a3ff8..dbb914b3 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -1,40 +1,30 @@ Installation ============ -We recommend run ``yabgp`` through python virtual-env from source -code or pip install +Python >= 3.13 is required. -From source code -~~~~~~~~~~~~~~~~ - -Use yabgp from source code: +From source (using uv) +~~~~~~~~~~~~~~~~~~~~~~ .. code:: bash - $ virtualenv yabgp-virl - $ source yabgp-virl/bin/activate $ git clone https://github.com/smartbgp/yabgp $ cd yabgp - $ pip install -r requirements.txt - $ cd bin - $ python yabgpd -h + $ uv sync + $ uv run yabgpd -h From pip ~~~~~~~~ -Use pip install - .. code:: bash - $ virtualenv yabgp-virl - $ source yabgp-virl/bin/activate $ pip install yabgp - $ which yabgpd - /home/yabgp/yabgp-virl/bin/yabgpd $ yabgpd -h -.. note:: +Using Docker +~~~~~~~~~~~~ + +.. code:: bash - For ``virtualenv``, you can install it from pip. And make sure you have installed ``python-dev`` based on - your operation system, for example Ubuntu, you can install it from ``apt-get install python-dev``. - otherwise, you may get error when install requirement from requirements.txt \ No newline at end of file + $ docker run -it smartbgp/yabgp:latest --bgp-afi_safi=ipv4 \ + --bgp-local_as=65022 --bgp-remote_addr=10.75.44.219 --bgp-remote_as=65022 diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index effefd88..bb93712b 100644 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -19,9 +19,10 @@ The simple way to start a yabgp agent is (There are four mandatory parameters): The default address family will be IPv4 unicast. If you need support other address family, you can use something like: -``` -$ yabgpd --bgp-local_addr=10.75.44.122 --bgp-local_as=100 --bgp-remote_addr=10.75.195.199 --bgp-remote_as=100 --bgp-afi_safi=ipv4_srte,flowspec,ipv4,bgpls -``` +.. code:: bash + + $ yabgpd --bgp-local_addr=10.75.44.122 --bgp-local_as=100 --bgp-remote_addr=10.75.195.199 \ + --bgp-remote_as=100 --bgp-afi_safi=ipv4_srte,flowspec,ipv4,bgpls that session will suport: IPv4 Unicast, IPv4 Flowspec, IPv4 SR TE and BGP linkstate. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..0bbed2e9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,88 @@ +[build-system] +requires = ["hatchling>=1.27.0"] +build-backend = "hatchling.build" + +[tool.hatch.version] +path = "yabgp/__init__.py" + +[project] +name = "yabgp" +dynamic = ["version"] +description = "Yet Another Border Gateway Protocol Python Implementation" +readme = "README.rst" +requires-python = ">=3.13" +license = { text = "Apache License 2.0" } +authors = [{ name = "SmartBGP project team", email = "xiaoquwl@gmail.com" }] +keywords = ["BGP", "SDN"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: Unix", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python", + "Programming Language :: Python :: 3.13", + "Topic :: System :: Networking", +] +dependencies = [ + "Flask==3.1.3", + "Flask-HTTPAuth==4.8.1", + "Twisted==25.5.0", + "netaddr==1.3.0", + "oslo.config==10.3.0", + "py-radix==1.1.0", + "simplejson==4.1.1", +] + +[project.urls] +Homepage = "https://smartbgp.github.io/" + +[project.scripts] +yabgpd = "yabgp.agent.cmd:main" + +[dependency-groups] +test = [ + "coverage>=7.0", + "pytest>=8.0", + "pytest-cov>=5.0", +] +dev = [ + "pyright>=1.1.402", + "ruff>=0.11.10", +] +docs = [ + "sphinx>=8.0", + "sphinx-rtd-theme>=3.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["yabgp"] + +[tool.hatch.build.targets.wheel.shared-data] +"etc/yabgp/yabgp.ini.sample" = "etc/yabgp/yabgp.ini.sample" + +[tool.hatch.build.targets.sdist] +include = [ + "/etc/yabgp/yabgp.ini.sample", + "/yabgp", + "/README.rst", +] + +[tool.coverage.run] +branch = true +source = ["yabgp"] +omit = ["yabgp/tests/*"] + +[tool.ruff] +line-length = 120 +target-version = "py313" + +[tool.ruff.lint] +select = ["E", "W", "F", "I", "UP"] + +[tool.ruff.lint.per-file-ignores] +# Module docstring before imports triggers false E402; no executable code precedes imports here +"yabgp/message/attribute/nlri/ipv4_unicast.py" = ["E402"] + diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 00000000..21e72c21 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,14 @@ +{ + "include": [ + "yabgp" + ], + "typeCheckingMode": "off", + "reportDeprecated": "error", + "exclude": [ + ".venv", + ".git", + "build", + "dist", + "doc" + ] +} diff --git a/redhat/python-yabgp.spec b/redhat/python-yabgp.spec deleted file mode 100644 index 8bed39f9..00000000 --- a/redhat/python-yabgp.spec +++ /dev/null @@ -1,74 +0,0 @@ -%{!?__python2: %global __python2 /usr/bin/python2} -%{!?python2_sitelib: %global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} - -Name: python-yabgp -Version: 0.1.4 -Release: 0%{?dist} -Summary: Yet Another BGP (Border Gateway Protocol) Python Implementation - -Group: Development/Libraries -License: APLv2 -URL: http://yabgp.readthedocs.org/en/latest/ -Source0: https://github.com/smartbgp/yabgp/archive/v%{version}.tar.gz -Source1: yabgp.service -BuildArch: noarch -Provides: yabgp-libs - -BuildRequires: python-setuptools -Requires: python2 >= 2.6 -#Requires: python-pbr >= 0.5.21, python-pymongo >= 3.0.3, python-netaddr >= 0.7.12, python-flask >= 0.10.1, python-pika >= 0.9.14, python-flask-httpauth >= 2.5.0, python-twisted >= 15.0.0, python-oslo-config >= 1.6.0 - -%description -YABGP python module - -%package -n yabgp -Summary: Yet Another BGP (Border Gateway Protocol) -Group: Applications/Internet -BuildRequires: systemd-units -Requires: systemd, yabgp-libs == %{version} - -%description -n yabgp -YABGP is a yet another Python implementation for BGP Protocol. It can be used -to establish BGP connections with all kinds of routers (include real -Cisco/HuaWei/Juniper routers and some router simulators like GNS3) and -receive/parse BGP messages for future analysis. - -%prep -%autosetup -n yabgp-%{version} - -%build -%{__python2} setup.py build - -%install -%{__python2} setup.py install -O1 --root ${RPM_BUILD_ROOT} - -# fix file locations -mv ${RPM_BUILD_ROOT}/usr/bin ${RPM_BUILD_ROOT}%{_sbindir} -mv ${RPM_BUILD_ROOT}/usr/etc ${RPM_BUILD_ROOT}/%{_sysconfdir} - -install -d %{buildroot}/%{_unitdir} -install %{SOURCE1} %{buildroot}/%{_unitdir}/ - -%post -n yabgp -%systemd_post yabgp.service - -%preun -n yabgp -%systemd_preun yabgp.service - -%postun -n yabgp -%systemd_postun_with_restart yabgp.service - -%files -%defattr(-,root,root,-) -%{python2_sitelib}/* -%doc LICENSE README.rst requirements.txt - -%files -n yabgp -%defattr(-,root,root,-) -%attr(755, root, root) %{_sbindir}/yabgpd -%dir %{_sysconfdir}/yabgp -%attr(744, root, root) %{_sysconfdir}/yabgp/* -%{_unitdir}/yabgp.service -%doc LICENSE README.rst - -%changelog diff --git a/redhat/yabgp.service b/redhat/yabgp.service deleted file mode 100644 index 69655b00..00000000 --- a/redhat/yabgp.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=YABGP -After=network.target - -[Service] -ConditionPathExists=/etc/yabgp/yabgp.ini -ExecStart=/usr/sbin/yabgp /etc/yabgp/yabgp.ini -ExecReload=/bin/kill -USR1 $MAINPID - -[Install] -WantedBy=multi-user.target diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2fec925d..00000000 --- a/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -pbr==2.0.0 -oslo.config==2.1.0 -Twisted==20.3.0 -Flask==1.0 -Flask-HTTPAuth==2.5.0 -netaddr==0.8.0 -future>=0.16.0 -py-radix==0.10.0 -simplejson==3.17.0 diff --git a/run_test.py b/run_test.py deleted file mode 100644 index ece3e022..00000000 --- a/run_test.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2015-2018 Cisco Systems, Inc. -# All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import unittest - - -def test(): - "test load and run" - tests = unittest.TestLoader().discover('.', pattern='test*.py') - result = unittest.TextTestRunner(verbosity=2).run(tests) - if not result.wasSuccessful(): - raise ValueError('unittest failed') - - -if __name__ == '__main__': - test() diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 8eb7f7b1..00000000 --- a/setup.cfg +++ /dev/null @@ -1,42 +0,0 @@ -[metadata] -name = yabgp -summary = Yet Another Border Gateway Protocol Python Implementation -license = Apache License 2.0 -author = SmartBGP project team -author-email = xiaoquwl@gmail.com -home-page = http://smartbgp.github.io/ -description-file = README.rst -platform = any -classifier = - Development Status :: 5 - Production/Stable - Environment :: Console - License :: OSI Approved :: Apache Software License - Topic :: System :: Networking - Natural Language :: English - Programming Language :: Python - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3.4 - Programming Language :: Python :: 3.5 - Operating System :: Unix -keywords = - BGP - SDN - -[build_sphinx] -all_files = 1 -build-dir = doc/build -source-dir = doc/source - -[global] -setup-hooks = - yabgp.hooks.setup_hook - -[files] -packages = - yabgp -data_files = - etc/yabgp/ = - etc/yabgp/yabgp.ini.sample -[entry_points] -console_scripts = - yabgpd = yabgp.agent.cmd:main diff --git a/setup.py b/setup.py deleted file mode 100644 index 358555eb..00000000 --- a/setup.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2015 Cisco Systems, Inc. -# All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import setuptools - -import yabgp.hooks - -yabgp.hooks.save_orig() - -setuptools.setup( - name='yabgp', - setup_requires=['pbr'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index dafdd65c..00000000 --- a/test-requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -nose==1.3.6 -flake8==2.4.0 -six>=1.10.0 -coverage -testrepository>=0.0.13 -testtools>=0.9.26 diff --git a/tools/rabbit_mongo.sh b/tools/rabbit_mongo.sh deleted file mode 100755 index eeead016..00000000 --- a/tools/rabbit_mongo.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2015 Cisco Systems, Inc. -# All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -echo "Please makesure you have installed docker and can run as no-root user" -echo "if yes, please press Enter..." -read - -# try to stop running containers -docker kill `docker ps | grep mongo | awk '{print $1}'` -docker kill `docker ps | grep rabbit | awk '{print $1}'` - -# clean containers -docker rm `docker ps -a | awk '{print $1}'` - - -# for rabbitmq -echo "Starting rabbitmq..." -docker run -d -p 5672:5672 -p 15672:15672 -e RABBITMQ_PASS="admin" tutum/rabbitmq - -# for mongodb -echo "starting mongodb replica set" - -cd ~ -for ((i=1; i<4; i++)); do - if ! [ -d data/mongo/node$i ]; then - mkdir -p data/mongo/node$i - fi -done - -SCRIPTPATH=`pwd -P` - -docker run -p 27017:27017 -p 27018:27018 -p 27019:27019 -d \ - -v /host/primary:$SCRIPTPATH/data/mongo/node1 -v /host/secondary:$SCRIPTPATH/data/mongo/node2 \ - tattsun/mongodb-replset - -sleep 30 - -echo "init mongodb replca set" - -mongo admin --eval "rs.initiate()" - -mongo_host_id=`docker ps | grep mongo | awk '{print $1}'` - -mongo admin --eval "rs.add(\"$mongo_host_id:27018\")" - -mongo admin --eval "rs.add(\"$mongo_host_id:27019\")" diff --git a/tox.ini b/tox.ini deleted file mode 100644 index d7ca90ef..00000000 --- a/tox.ini +++ /dev/null @@ -1,53 +0,0 @@ -# Tox (http://tox.testrun.org/) is a tool for running tests -# in multiple virtualenvs. This configuration file will run the -# test suite on all supported python versions. To use it, "pip install tox" -# and then run "tox" from this directory. - -[tox] -# List the environment that will be run by default -minversion = 1.6 -envlist = py27,py36,docs,pep8 -skipsdist = True - -[testenv] -setenv = VIRTUAL_ENV={envdir} - LANG=en_US.UTF-8 - LANGUAGE=en_US:en - LC_ALL=C -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - -[testenv:py27] -commands = coverage erase - coverage run run_test.py - coverage report -m -[testenv:py36] -commands = coverage erase - coverage run run_test.py - coverage report -m - -[testenv:docs] -deps= - sphinx==1.5.5 - sphinx_rtd_theme - sphinxcontrib-httpdomain==1.1.7 - -r{toxinidir}/requirements.txt -commands = sphinx-build -W -b html doc/source doc/build - -[testenv:pep8] -sitepackages = False -commands = - flake8 {posargs} - -[flake8] -# E712 is ignored on purpose, since it is normal to use 'column == true' -# in sqlalchemy. -# H803 skipped on purpose per list discussion. -# E125 is deliberately excluded. See https://github.com/jcrocholl/pep8/issues/126 -# The rest of the ignores are TODOs -# New from hacking 0.9: E129, E131, E265, E713, H407, H405, H904 -# Stricter in hacking 0.9: F402 -# E251 Skipped due to https://github.com/jcrocholl/pep8/issues/301 - -max-line-length=120 -exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..26b0fb12 --- /dev/null +++ b/uv.lock @@ -0,0 +1,934 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "automat" +version = "25.4.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/0f/d40bbe294bbf004d436a8bcbcfaadca8b5140d39ad0ad3d73d1a8ba15f14/automat-25.4.16.tar.gz", hash = "sha256:0017591a5477066e90d26b0e696ddc143baafd87b588cfac8100bc6be9634de0", size = 129977, upload-time = "2025-04-16T20:12:16.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/ff/1175b0b7371e46244032d43a56862d0af455823b5280a50c63d99cc50f18/automat-25.4.16-py3-none-any.whl", hash = "sha256:04e9bce696a8d5671ee698005af6e5a9fa15354140a87f4870744604dcdd3ba1", size = 42842, upload-time = "2025-04-16T20:12:14.447Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "certifi" +version = "2026.4.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "click" +version = "8.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/e4/796662cd90cf80e3a363c99db2b88e0e394b988a575f60a17e16440cd011/click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973", size = 350843, upload-time = "2026-05-17T00:47:58.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/ae/8e92f8058baf87f6c7d86ee7e457668690195cc77efedb8d3797a06e3940/click-8.4.0-py3-none-any.whl", hash = "sha256:40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81", size = 116147, upload-time = "2026-05-17T00:47:56.842Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "constantly" +version = "23.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/6f/cb2a94494ff74aa9528a36c5b1422756330a75a8367bf20bd63171fc324d/constantly-23.10.4.tar.gz", hash = "sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd", size = 13300, upload-time = "2023-10-28T23:18:24.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/40/c199d095151addf69efdb4b9ca3a4f20f70e20508d6222bffb9b76f58573/constantly-23.10.4-py3-none-any.whl", hash = "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9", size = 13547, upload-time = "2023-10-28T23:18:23.038Z" }, +] + +[[package]] +name = "coverage" +version = "7.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/7f/d0720730a397a999ffc0fd3f5bebef347338e3a47b727da66fbb228e2ff2/coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", size = 919489, upload-time = "2026-05-10T18:02:31.397Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/76/b7c66ee3c66e1b0f9d894c8125983aa0c03fb2336f2fd16559f9c966157f/coverage-7.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f2bbb8254370eb4c628ff3d6fa8a7f74ddc40565394d4f7ab791d1fe568e37ef", size = 219990, upload-time = "2026-05-10T18:00:38.887Z" }, + { url = "https://files.pythonhosted.org/packages/b3/af/e567cbad5ba69c013a50146dfa886dc7193361fda77521f51274ff620e1b/coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66", size = 220365, upload-time = "2026-05-10T18:00:40.864Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/9ad575d505b4d805b254febc8a5b338a2efe278f8786e56ff1cb8413f9c3/coverage-7.14.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:22a7e06a5f11a757cdfe79018e9095f9f69ae283c5cd8123774c788deec8717b", size = 251363, upload-time = "2026-05-10T18:00:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5f/b5370068b2f57787454592ed7dcd1002f0f1703b7db1fa30f6a325a4ca6e/coverage-7.14.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9d1aa57a1dc8e05bdc42e81c5d671d849577aeedf279f4c449d6d286f9ed88ca", size = 253961, upload-time = "2026-05-10T18:00:44.079Z" }, + { url = "https://files.pythonhosted.org/packages/29/1e/51adf17738976e8f2b85ddef7b7aa12a0838b056c92f175941d8862767c1/coverage-7.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c1a51bcfddf645b3bb7ec333d9e94393a8e94f55642380fa8a9a5a9e636cb7", size = 255193, upload-time = "2026-05-10T18:00:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7b/5bfd7ac1df3b881c2ac7a5cbc99c7609e6296c402f5ef587cd81c6f355b3/coverage-7.14.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a841fae2fadcae4f438d43b6ccc4aac2ad609f47cdb6cfdce60cbb3fe5ca7bc2", size = 257326, upload-time = "2026-05-10T18:00:47.173Z" }, + { url = "https://files.pythonhosted.org/packages/7d/38/1d37d316b174fad3843a1d76dbdfe4398771c9ecd0515935dd9ece9cd627/coverage-7.14.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c79d2319cabef1fe8e86df73371126931550804738f78ad7d31e3aad85a67367", size = 251582, upload-time = "2026-05-10T18:00:49.152Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/746704f95980ba220214e1a41e18cec5aea80a898eaa53c51bf2d645ff36/coverage-7.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b23b0c6f0b1db6ad769b7050c8b641c0bf215ded26c1816955b17b7f26edfa9", size = 253325, upload-time = "2026-05-10T18:00:51.252Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b9/bbe87206d9687b192352f893797825b5f5b15ecd3aa9c68fbff0c074d77b/coverage-7.14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:55d3089079ce181a4566b1065ab28d2575eb76d8ac8f81f4fcda2bf037fee087", size = 251291, upload-time = "2026-05-10T18:00:52.816Z" }, + { url = "https://files.pythonhosted.org/packages/46/57/b8cdb12ac0d73ef0243218bd5e22c9df8f92edab8018213a86aec67c5324/coverage-7.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:49c005cba1e2f9677fb2845dcdf9a2e72a52a17d63e8231aaaae35d9f50215ef", size = 255448, upload-time = "2026-05-10T18:00:54.548Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d4/5002019538b2036ce3c84340f54d2fd5100d55b0a6b0894eee56128d03c7/coverage-7.14.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9117377b823daa28aa8635fbb08cda1cd6be3d7143257345459559aeef852d52", size = 251110, upload-time = "2026-05-10T18:00:56.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/20c5009477660f084e6ed60bc02a91894b8e234e617e86ecfd9aaf78e27b/coverage-7.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b79d646cf46d5cf9a9f40281d4441df5849e445726e369006d2b117710b33fe", size = 252885, upload-time = "2026-05-10T18:00:57.967Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ab/3cf6427ac9c1f1db747dbb1ce71dde47984876d4c2cfd018a3fef0a78d4d/coverage-7.14.0-cp313-cp313-win32.whl", hash = "sha256:fb609b3658479e33f9516d46f1a89dbb9b6c261366e3a11844a96ec487533dae", size = 222539, upload-time = "2026-05-10T18:00:59.581Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b8/9228523e80321c2cb4880d1f589bc0171f2f71432c35118ad04dc01decce/coverage-7.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0773d8329cf32b6fd222e4b52622c61fe8d503eb966cfc8d3c3c10c96266d50e", size = 223344, upload-time = "2026-05-10T18:01:01.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/99/118daa192f95e3a6cb2740100fbf8797cda1734b4134ef0b5d501a7fa8f3/coverage-7.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:b4e26a0f1b696faf283bffe5b8569e44e336c582439df5d53281ab89ee0cba96", size = 221966, upload-time = "2026-05-10T18:01:03.16Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f1/a46cc0c013be170216253184a32366d7cbdb9252feaec866b05c2d12a894/coverage-7.14.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:953f521ca9445300397e65fda3dca58b2dbd68fee983777420b57ac3c77e9f90", size = 220679, upload-time = "2026-05-10T18:01:05.058Z" }, + { url = "https://files.pythonhosted.org/packages/64/8c/9c30a3d311a34177fa432995be7fbfc64477d8bac5630bd38055b1c9b424/coverage-7.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:98af83fd65ae24b1fdd03aaead967a9f523bcd2f1aab2d4f3ffda65bb568a6f1", size = 221033, upload-time = "2026-05-10T18:01:07.002Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cd/3fb5e06c3badefd0c1b47e2044fdca67f8220a4ec2e7fcfb476aa0a67c6c/coverage-7.14.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:668b92e6958c4db7cf92e81caac328dfbbdbb215db2850ad28f0cbe1eea0bfbd", size = 262333, upload-time = "2026-05-10T18:01:08.903Z" }, + { url = "https://files.pythonhosted.org/packages/a8/e6/fbc322325c7294d3e22c1ad6b79e45d0806b25228c8e5842aed6d8169aa7/coverage-7.14.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9fbd898551762dea00d3fef2b1c4f99afd2c6a3ff952ea07d60a9bd5ed4f34bc", size = 264410, upload-time = "2026-05-10T18:01:10.531Z" }, + { url = "https://files.pythonhosted.org/packages/08/92/c497b264bec1673c47cc77e26f760fcda4654cabf1f39546d1a23a3b8c35/coverage-7.14.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68af363c07ecd8d4b7d4043d85cb376d7d227eceb54e5323ee45da73dbd3e426", size = 266836, upload-time = "2026-05-10T18:01:12.19Z" }, + { url = "https://files.pythonhosted.org/packages/78/fc/045da320987f401af5d2815d351e8aa799aec859f60e29f445e3089eeedb/coverage-7.14.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e57054a583da8ac55edf24117ea4c9133032cfc4cf72aa2d48c1e5d4b52f899", size = 267974, upload-time = "2026-05-10T18:01:13.926Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ae/227b1e379497fb7a4fc3286e620f80c8a1e7cec66d45695a01639eb1af65/coverage-7.14.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3499459bbcdd51a65b64c35ab7ed2764eaf3cba826e0df3f1d7fe2e102b70b", size = 261578, upload-time = "2026-05-10T18:01:15.564Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f5/3570342900f2acea31d33ff1590c5d8bac1a8e1a2e1c6d34a5d5e61de681/coverage-7.14.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:45899ec2138a4346ed34d601dedf5076fb74edf2d1dd9dc76a78e82397edee90", size = 264394, upload-time = "2026-05-10T18:01:17.607Z" }, + { url = "https://files.pythonhosted.org/packages/16/29/de1bbc01c935b28f89b1dc3db85b011c055e843a8e5e3b83141c3f80af7f/coverage-7.14.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8767486808c436f05b23ab98eb963fb29185e32a9357a166971685cb3459900f", size = 262022, upload-time = "2026-05-10T18:01:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/35/95/f53890b0bf2fc10ab168e05d38869215e73ca24c4cb521c3bb0eb62fe16b/coverage-7.14.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a3b5ddfd6aa7ddad53ee3edb231e88a2151507a43229b7d71b953916deca127d", size = 265732, upload-time = "2026-05-10T18:01:21.494Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ea/c919e259081dd2bdf0e43b87209709ba7ec2e4117c2a7f5185379c43463c/coverage-7.14.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:63df0fe568e698e1045792399f8ab6da3a6c2dce3182813fb92afa2641087b47", size = 260921, upload-time = "2026-05-10T18:01:23.533Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2c/c2831889705a81dc5d1c6ca12e4d8e9b95dfc146d153488a6c0ea685d28e/coverage-7.14.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:827d6397dbd95144939b18f89edf31f63e1f99633e8d5f32f22ba8bdda567477", size = 263109, upload-time = "2026-05-10T18:01:25.165Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a9/2fcae5003cac3d63fe344d2166243c2756935f48420863c5272b240d550b/coverage-7.14.0-cp313-cp313t-win32.whl", hash = "sha256:7bf43e000d24012599b879791cff41589af90674722421ef11b11a5431920bab", size = 223212, upload-time = "2026-05-10T18:01:27.157Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bb/18e94d7b14b9b398164197114a587a04ab7c9fdbe1d237eef57311c5e883/coverage-7.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3f5549365af25d770e06b1f8f5682d9a5637d06eb494db91c6fa75d3950cc917", size = 224272, upload-time = "2026-05-10T18:01:29.107Z" }, + { url = "https://files.pythonhosted.org/packages/db/56/4f14fad782b035c81c4ffd09159e7103d42bb1d93ac8496d04b90a11b7da/coverage-7.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6d160217ec6fe890f16ad3a9531761589443749e448f91986c972714fad361c8", size = 222530, upload-time = "2026-05-10T18:01:31.151Z" }, + { url = "https://files.pythonhosted.org/packages/1c/18/b9a6586d73992807c26f9a5f274131be3d76b56b18a82b9392e2a25d2e45/coverage-7.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d", size = 220036, upload-time = "2026-05-10T18:01:33.057Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63", size = 220368, upload-time = "2026-05-10T18:01:34.705Z" }, + { url = "https://files.pythonhosted.org/packages/69/aa/c12e52a5ba148d9995229d557e3be6e554fe469addc0e9241b2f0956d8ea/coverage-7.14.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212", size = 251417, upload-time = "2026-05-10T18:01:36.949Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3", size = 253924, upload-time = "2026-05-10T18:01:38.985Z" }, + { url = "https://files.pythonhosted.org/packages/33/c4/59c3de0bd1b538824173fd518fed51c1ce740ca5ed68e74545983f4053a9/coverage-7.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97", size = 255269, upload-time = "2026-05-10T18:01:40.957Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a9/36dfa153a62040296f6e7febfdb20a5720622f6ef5a81a41e8237b9a5344/coverage-7.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8", size = 257583, upload-time = "2026-05-10T18:01:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/26/7b/cc2c048d4114d9ab1c2409e9ee365e5ae10736df6dffcfc9444effa6c708/coverage-7.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb", size = 251434, upload-time = "2026-05-10T18:01:44.537Z" }, + { url = "https://files.pythonhosted.org/packages/ee/df/6770eaa576e604575e9a78055313250faef5faa84bd6f71a39fece519c43/coverage-7.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe", size = 253280, upload-time = "2026-05-10T18:01:46.175Z" }, + { url = "https://files.pythonhosted.org/packages/ad/9e/1c0264514a3f98259a6d64765a397b2c8373e3ba59ee722a4802d3ec0c61/coverage-7.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa", size = 251241, upload-time = "2026-05-10T18:01:48.732Z" }, + { url = "https://files.pythonhosted.org/packages/64/16/4efdf3e3c4079cdbf0ece56a2fea872df9e8a3e15a13a0af4400e1075944/coverage-7.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5", size = 255516, upload-time = "2026-05-10T18:01:50.819Z" }, + { url = "https://files.pythonhosted.org/packages/93/69/b1de96346603881b3d1bc8d6447c83200e1c9700ffbaff926ba01ff5724c/coverage-7.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c", size = 251059, upload-time = "2026-05-10T18:01:52.773Z" }, + { url = "https://files.pythonhosted.org/packages/a4/66/2881853e0363a5e0a724d1103e53650795367471b6afb234f8b49e713bc6/coverage-7.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca", size = 252716, upload-time = "2026-05-10T18:01:54.506Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/0d3305d002c41dcde873dbe456491e663dc55152ca526b630b5c47efd62f/coverage-7.14.0-cp314-cp314-win32.whl", hash = "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828", size = 222788, upload-time = "2026-05-10T18:01:56.487Z" }, + { url = "https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d", size = 223600, upload-time = "2026-05-10T18:01:58.497Z" }, + { url = "https://files.pythonhosted.org/packages/00/70/a18c408e674bc26281cadaedc7351f929bd2094e191e4b15271c30b084cc/coverage-7.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9", size = 222168, upload-time = "2026-05-10T18:02:00.411Z" }, + { url = "https://files.pythonhosted.org/packages/3d/89/2681f071d238b62aff8dfc2ab44fc24cfdb38d1c01f391a80522ff5d3a16/coverage-7.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1", size = 220766, upload-time = "2026-05-10T18:02:02.313Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c7/c987babafd9207ffa1995e1ef1f9b26762cf4963aa768a66b6f0501e4616/coverage-7.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c", size = 221035, upload-time = "2026-05-10T18:02:04.017Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e9/d6a5ac3b333088143d6fc877d398a9a674dc03124a2f776e131f03864823/coverage-7.14.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84", size = 262405, upload-time = "2026-05-10T18:02:05.915Z" }, + { url = "https://files.pythonhosted.org/packages/38/b1/e70838d29a7c08e22d44398a46db90815bbcbf28de06992bd9210d1a8d8e/coverage-7.14.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436", size = 264530, upload-time = "2026-05-10T18:02:07.582Z" }, + { url = "https://files.pythonhosted.org/packages/6b/73/5c31ef97763288d03d9995152b96d5475b527c63d91c84b01caea894b83a/coverage-7.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a", size = 266932, upload-time = "2026-05-10T18:02:09.401Z" }, + { url = "https://files.pythonhosted.org/packages/e1/76/dd56d80f29c5f05b4d76f7e7c6d47cafacae017189c75c5759d24f9ff0cc/coverage-7.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f", size = 268062, upload-time = "2026-05-10T18:02:11.399Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c7/27ba85cd5b95614f159ff93ebff1901584a8d192e2e5e24c4943a7453f59/coverage-7.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb", size = 261504, upload-time = "2026-05-10T18:02:13.257Z" }, + { url = "https://files.pythonhosted.org/packages/13/2e/e8149f60ab5d5684c6eee881bdf34b127115cddbb958b196768dd9d63473/coverage-7.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490", size = 264398, upload-time = "2026-05-10T18:02:15.063Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7f/1261b025285323225f4b4abffa5a643649dfd67e25ddca7ebcbdea3b7cb3/coverage-7.14.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9", size = 262000, upload-time = "2026-05-10T18:02:16.756Z" }, + { url = "https://files.pythonhosted.org/packages/d3/dc/829c54f60b9d08389439c00f813c752781c496fc5788c78d8006db4b4f2b/coverage-7.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020", size = 265732, upload-time = "2026-05-10T18:02:18.817Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b0/70bd1419941652fa062689cba9c3eeafb8f5e6fbb890bce41c3bdda5dbd6/coverage-7.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6", size = 260847, upload-time = "2026-05-10T18:02:20.528Z" }, + { url = "https://files.pythonhosted.org/packages/f2/73/be40b2390656c654d35ea0015ea7ba3d945769cf80790ad5e0bb2d56d2ba/coverage-7.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db", size = 263166, upload-time = "2026-05-10T18:02:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/29/55/4a643f712fcf7cf2881f8ec1e0ccb7b164aff3108f69b51801246c8799f2/coverage-7.14.0-cp314-cp314t-win32.whl", hash = "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2", size = 223573, upload-time = "2026-05-10T18:02:24.11Z" }, + { url = "https://files.pythonhosted.org/packages/27/96/3acae5da0953be042c0b4dea6d6789d2f080701c77b88e44d5bd41b9219b/coverage-7.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644", size = 224680, upload-time = "2026-05-10T18:02:25.896Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/6ab5d2dd8325d838737c6f8d83d62eb6230e0d70b87b51b57bbfd08fa767/coverage-7.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b", size = 222703, upload-time = "2026-05-10T18:02:27.822Z" }, + { url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "flask" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, +] + +[[package]] +name = "flask-httpauth" +version = "4.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/f4/6957215e827021eeb7d8de9f59b1864d73933b04851e59272708cb6e5d2b/flask_httpauth-4.8.1.tar.gz", hash = "sha256:88499b22f1353893743c3cd68f2ca561c4ad9ef75cd6bcc7f621161cd0e80744", size = 38993, upload-time = "2026-03-28T19:45:24.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/da/624c87bf6c13107ceab8ee23815d9468e47d89c7480c1dc9af39b08eb290/flask_httpauth-4.8.1-py3-none-any.whl", hash = "sha256:0080393d70e12327781f7509115175ec5e47209816489a620d4fd39e20cea2e8", size = 9651, upload-time = "2026-03-28T19:45:23.155Z" }, +] + +[[package]] +name = "hyperlink" +version = "21.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/51/1947bd81d75af87e3bb9e34593a4cf118115a8feb451ce7a69044ef1412e/hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b", size = 140743, upload-time = "2021-01-08T05:51:20.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4", size = 74638, upload-time = "2021-01-08T05:51:22.906Z" }, +] + +[[package]] +name = "idna" +version = "3.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + +[[package]] +name = "incremental" +version = "24.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/3c/82e84109e02c492f382c711c58a3dd91badda6d746def81a1465f74dc9f5/incremental-24.11.0.tar.gz", hash = "sha256:87d3480dbb083c1d736222511a8cf380012a8176c2456d01ef483242abbbcf8c", size = 24000, upload-time = "2025-11-28T02:30:17.861Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/55/0f4df2a44053867ea9cbea73fc588b03c55605cd695cee0a3d86f0029cb2/incremental-24.11.0-py3-none-any.whl", hash = "sha256:a34450716b1c4341fe6676a0598e88a39e04189f4dce5dc96f656e040baa10b3", size = 21109, upload-time = "2025-11-28T02:30:16.442Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "netaddr" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/90/188b2a69654f27b221fba92fda7217778208532c962509e959a9cee5229d/netaddr-1.3.0.tar.gz", hash = "sha256:5c3c3d9895b551b763779ba7db7a03487dc1f8e3b385af819af341ae9ef6e48a", size = 2260504, upload-time = "2024-05-28T21:30:37.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/cc/f4fe2c7ce68b92cbf5b2d379ca366e1edae38cccaad00f69f529b460c3ef/netaddr-1.3.0-py3-none-any.whl", hash = "sha256:c2c6a8ebe5554ce33b7d5b3a306b71bbb373e000bbbf2350dd5213cc56e3dbbe", size = 2262023, upload-time = "2024-05-28T21:30:34.191Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "oslo-config" +version = "10.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "netaddr" }, + { name = "oslo-i18n" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rfc3986" }, + { name = "stevedore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/a6/aaf41cba43f8934d9c5db35f49fd8aa083279831f11974bea0816c593891/oslo_config-10.3.0.tar.gz", hash = "sha256:c405a40a8b05aa97bb5c24bb0b849981a7a5b7d56304df40632722312c58eaca", size = 164302, upload-time = "2026-02-17T15:19:16.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/4c/f6be02e23c8764683a891839a6431131bbbd8e93ab2701b1edde3493251b/oslo_config-10.3.0-py3-none-any.whl", hash = "sha256:b17d983bd1845087d282f19d583cae0da5bfe725dc6884c9a7d50454839640c9", size = 132620, upload-time = "2026-02-17T15:19:14.978Z" }, +] + +[[package]] +name = "oslo-i18n" +version = "6.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pbr" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/4e/0ed2248dfc4c8e993064b3b7419835fc1f1adbab6917f41a011157ed50d5/oslo_i18n-6.7.2.tar.gz", hash = "sha256:b1241ad3eee216e9dc9acb4336fce0bd79c4c286751ee70dfa42ff2f9763d34f", size = 50005, upload-time = "2026-02-17T16:07:43.571Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/d0/0c350384b916ad046dd0a7920e15a9ab539b5639ef8c902c76fb4bbfd256/oslo_i18n-6.7.2-py3-none-any.whl", hash = "sha256:5505cfc03a917b448bfaaf6f1221a1c36efbcc1a5b8c29407824bf5b056c5e0e", size = 47721, upload-time = "2026-02-17T16:07:42.558Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pbr" +version = "7.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/ab/1de9a4f730edde1bdbbc2b8d19f8fa326f036b4f18b2f72cfbea7dc53c26/pbr-7.0.3.tar.gz", hash = "sha256:b46004ec30a5324672683ec848aed9e8fc500b0d261d40a3229c2d2bbfcedc29", size = 135625, upload-time = "2025-11-03T17:04:56.274Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/db/61efa0d08a99f897ef98256b03e563092d36cc38dc4ebe4a85020fe40b31/pbr-7.0.3-py2.py3-none-any.whl", hash = "sha256:ff223894eb1cd271a98076b13d3badff3bb36c424074d26334cd25aebeecea6b", size = 131898, upload-time = "2025-11-03T17:04:54.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "py-radix" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/27/b32febece1ce72018b25955b606ee35fe6e8884b633ace7a1fa98b90277c/py_radix-1.1.0.tar.gz", hash = "sha256:4abdb4e4969aaeef40eeb186bada2911462183788a6f445b971d18aa5d1af9be", size = 27948, upload-time = "2025-12-04T19:24:55.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/bb/d417a62985ce32e62e5f9498c10ecb8678e0d507199255775a26ed5d2545/py_radix-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25d4d0af82d3163bd36b0352edff88e83aeab32d0863cbd4a13d05d82125e6c4", size = 22044, upload-time = "2025-12-04T19:24:18.97Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f7/03e2a2a3568b620e28b6b8d6feca217822a065af518f997fa20b6e5f4e1f/py_radix-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b2b3371951317143b6b5ec54f0f55d32722ff166ac4b83bc07c7dee292f88e8", size = 22262, upload-time = "2025-12-04T19:24:19.914Z" }, + { url = "https://files.pythonhosted.org/packages/fe/84/8e3ea944d0f8b9c3473b70b4c8e86e04fb774fa7b9968e9736756006d1fe/py_radix-1.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6673273e3f1fd1311a3228d67a5717b31a4ffa2829e79e5b79435836f34a0fc", size = 58197, upload-time = "2025-12-04T19:24:20.866Z" }, + { url = "https://files.pythonhosted.org/packages/50/67/6c178e018335eeb26bf1e297c490c21fffa173b49540e35d6fa9fce6f5be/py_radix-1.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc97d274ab70810ca9de81468ff985090816d5fb02be461d184a7a209e07586c", size = 57679, upload-time = "2025-12-04T19:24:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/40/f0/5de291bf7de0885d7ddc3ff4146241dd66228f700bbda45852354112358f/py_radix-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e73449df8422558d441aef759ffc7c49560bf231c4ba88d911904564da657913", size = 56773, upload-time = "2025-12-04T19:24:23.111Z" }, + { url = "https://files.pythonhosted.org/packages/30/ae/16869c59b0aaa3f1ded68cc1b800e9621573c2bb2a2532bcb5f10de88bb4/py_radix-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f568e0388f1b5ddb0b47e6758f786de8ca9cb57d3e6f06fe1f69abccc098ba20", size = 56939, upload-time = "2025-12-04T19:24:24.299Z" }, + { url = "https://files.pythonhosted.org/packages/69/41/26cd3ba2cf5f63004039502d6b04c7e579c042a7b8c7ecfecd8017c1632f/py_radix-1.1.0-cp313-cp313-win32.whl", hash = "sha256:960d691660096da5b40142d89c66d06aca2c76a632d1dd5272633471fe075717", size = 21472, upload-time = "2025-12-04T19:24:25.582Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c5/74d36290fdfe1b37f589ab2af3222111805ad6ccc27fc9bc163140c44268/py_radix-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e65852ffcd3ace108d65a2143dbf646286d95b4f28bbfaeb94e6ef89692aecfa", size = 23374, upload-time = "2025-12-04T19:24:26.519Z" }, + { url = "https://files.pythonhosted.org/packages/39/c3/71d67c6782eec36ef507208d3cc5f1f0abb2d1ddfec4f2259c7b19d09edb/py_radix-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:f63681e132ebe2b6d61598787a5e0f5b300e7c9c289c0512c57c15c232aa9430", size = 21063, upload-time = "2025-12-04T19:24:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/5c/07/2588fe3a109dac27ef3d9d98dbd4adfb91a979f57e2b03776087bd532308/py_radix-1.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:87dbb4e1aabc014418b35a8b80872059e011a78ce41306db97b07f3582b95081", size = 22086, upload-time = "2025-12-04T19:24:28.78Z" }, + { url = "https://files.pythonhosted.org/packages/8a/25/528da092f66c8cb82cc66e49ddf5816cb7d8c08637e273fef0b69218b399/py_radix-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:94b85334b4e5d4ca9368dd74e3d3759bd389e186a63fe85a4f6c9393a41fc93b", size = 22267, upload-time = "2025-12-04T19:24:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5f/20b6ddeb625ab0c45840b33ec1cd46b6b7828e1ae6c18a66f9afb8eeb719/py_radix-1.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fb9595fba0eace7df4d8756d015a7327058180356dc64c3356b3a36aa140b3", size = 58197, upload-time = "2025-12-04T19:24:31.04Z" }, + { url = "https://files.pythonhosted.org/packages/34/f8/2b54102ff504f77e59552df5185afa750ce074a0c72e80e76fc7580cbd5c/py_radix-1.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:134ff75a354d0dd0ff650f70d5f4511d981eaf06f4f958cf4a62959ddafce34d", size = 57672, upload-time = "2025-12-04T19:24:32.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/52/2b27d7d9f651c5d0299309d21309d98bdfb969e6ac09db87639f02d02237/py_radix-1.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d03bef08134def5cd23a662141dbe8091f551401c8ad5c2370a558d8ff868dbb", size = 56743, upload-time = "2025-12-04T19:24:33.257Z" }, + { url = "https://files.pythonhosted.org/packages/e2/80/846e650ab5a68cdc69519bbe17c0a5c6340412efdffbd8a31ec492bc096d/py_radix-1.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e538d8deee5cf2ee9033fc27fbfa9a9702d5fd95e99a8530e9630b0dcce7c3b", size = 56901, upload-time = "2025-12-04T19:24:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/5e/3c/6b8d3a10b8bcd429cd785153357ae3314d44347c154052d891c26e986241/py_radix-1.1.0-cp314-cp314-win32.whl", hash = "sha256:05a03d3513101ffd7ff81313fb87b81825adaedea7d5baceb7ad04dd2eb05c28", size = 21919, upload-time = "2025-12-04T19:24:35.215Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7a/28a4f8e627933a32e08c9e49cd919ebbf040a297738eaa570488ce721967/py_radix-1.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:70b3bcb3b077747184e91f3aca32735d8c0f4eba875c218f43f338caf802f205", size = 23820, upload-time = "2025-12-04T19:24:36.099Z" }, + { url = "https://files.pythonhosted.org/packages/34/4c/02e6224e16c29b1d8c775c5c54db19e095566606ad4ebacb5f5d1dbbf396/py_radix-1.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a484ab205a5002487fb62498d6881e8033e39d5ecc9abc0893878421cdfc68a", size = 21492, upload-time = "2025-12-04T19:24:36.967Z" }, + { url = "https://files.pythonhosted.org/packages/0f/49/f87f061f595a6e0d739673460d9ca039e2e58f18a25e94c058cf9d000472/py_radix-1.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eb3699ef63f5ccd2d820f242b900ace8e402af409b7613ed2822c9514e0a25c8", size = 22554, upload-time = "2025-12-04T19:24:37.863Z" }, + { url = "https://files.pythonhosted.org/packages/39/2c/84950ebda47b46b313ad76eb5071fc809490fa38314b8aa612977cdb8c0a/py_radix-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:45124628f68d14aee80e0468334a88560be9f6ef5dc2121a21b5a4e4787bb7dd", size = 22805, upload-time = "2025-12-04T19:24:38.78Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fd/8c812e517092cafef237d0cdadd6d5bca85fed6be0802256224f1a244601/py_radix-1.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:473d7d9182ecea2c5d78e4a78423654ff77f9a4ee5a6a4c754338c262022ec20", size = 61075, upload-time = "2025-12-04T19:24:39.645Z" }, + { url = "https://files.pythonhosted.org/packages/0e/48/4c9c693a47940ca301871cc1a04022b648af63af60ea7b213ee48269930a/py_radix-1.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd8002c2779c8aadc973e08ec24e7452a3bd8cda310248ae2ff570dff407e5d3", size = 59648, upload-time = "2025-12-04T19:24:40.574Z" }, + { url = "https://files.pythonhosted.org/packages/f1/34/8b02d42d843d6b63860d69877e63610ca821d67e0d22ab20b5105452e33c/py_radix-1.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ba22cf1897e5d18ded797ab7bb7403f9707b3c2e96f0f28a43d71daf6fe974d7", size = 59509, upload-time = "2025-12-04T19:24:41.568Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8c/23a5a75d26981726c2dba8a9e607d0c57723f7381466a1393f5b176db63c/py_radix-1.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43b956232a7cafaaa5f0c24534295b6a4056829578ed2fbf1e3d7ac4b27a4e4a", size = 58898, upload-time = "2025-12-04T19:24:42.546Z" }, + { url = "https://files.pythonhosted.org/packages/61/1f/33234db400ef4644971b57c08d7c018ebabf97abe74d3a4e3d726bb8778b/py_radix-1.1.0-cp314-cp314t-win32.whl", hash = "sha256:d2391a9c113bd2fdccf7acea4becfebf68160e8d61b220be6c8e0a5c71fe37e8", size = 22533, upload-time = "2025-12-04T19:24:43.46Z" }, + { url = "https://files.pythonhosted.org/packages/f6/71/2031d7d883b6d0617228276e2d5fad19cfaae86fd450e4c269f475136fdd/py_radix-1.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2cfa00c8c5397437153893bfe2f2b9c2e6fbcfe45f0b6e6a85870ba0aa38515d", size = 24499, upload-time = "2025-12-04T19:24:44.373Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4b/de5508fadfc46af4951fc8b969b9c606e0a4a322aebeb4749aca52a76ce2/py_radix-1.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:d2dbf7ed1e6e52ce7d9209dcb87dcc902058f1635dccb8f6ed0e52cf04817daa", size = 21997, upload-time = "2025-12-04T19:24:45.314Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyright" +version = "1.1.409" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/4e/3aa27f74211522dba7e9cbc3e74de779c6d4b654c54e50a4840623be8014/pyright-1.1.409.tar.gz", hash = "sha256:986ee05beca9e077c165758ad123667c679e050059a2546aa02473930394bc93", size = 4430434, upload-time = "2026-04-23T11:02:03.799Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/6b/330d8ebae582b30c2959a1ef4c3bc344ebde48c2ff0c3f113c4710735e11/pyright-1.1.409-py3-none-any.whl", hash = "sha256:aa3ea228cab90c845c7a60d28db7a844c04315356392aa09fafcee98c8c22fb3", size = 6438161, upload-time = "2026-04-23T11:02:01.309Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "rfc3986" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" }, + { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" }, + { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" }, + { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" }, + { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" }, + { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[package]] +name = "simplejson" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/2a/54837395a3487c725669428d513293612a48d82b95a0642c936932e5d898/simplejson-4.1.1.tar.gz", hash = "sha256:c08eb9f7a90f77ae470e19a07472e9a79ebc0d1c2315d86a72767665bd5ba79f", size = 118860, upload-time = "2026-04-24T19:24:59.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/a9/47b445eeb559c9593453a0648e0fd6d08e8adff64dd5e5ced66726da8a09/simplejson-4.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dff52fc7af272e84fc21cc5a06c927c823ca6ae00af14f3b0d7707b42775ed98", size = 113160, upload-time = "2026-04-24T19:23:26.033Z" }, + { url = "https://files.pythonhosted.org/packages/4c/65/cb72db31523c164dea5dc55b02dad065a40c478856bc7534b279d2b51906/simplejson-4.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:971aed0647ad6e840a3943bec812fcda5f2d26a5497a4981d1fb49aa4f9a396c", size = 91521, upload-time = "2026-04-24T19:23:27.572Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e5/54cb7c50ad5fdc1e0a86b7df4b135c2cbd5c4623605aa94466659098e8da/simplejson-4.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:249e2e220aa6d9b9d936bde84eb7bf79d5b6c5a8273c6e411f8b1635a9073f2d", size = 91407, upload-time = "2026-04-24T19:23:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/21a3ede87f0bf82d6c7bcb90480d50a6490eb974c6ab20881188e440957c/simplejson-4.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e5cdd6a5d52299f345c15ab5678cc4249e24f383f361d986afbc3c7072a6b6b", size = 192451, upload-time = "2026-04-24T19:23:30.56Z" }, + { url = "https://files.pythonhosted.org/packages/59/df/9903edd3102bf0b5984edfcb90c88612330996efa3b4fbf8a971d6e17839/simplejson-4.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:642cec364e0676e2d5a73fa4d31d0c7c55886997caa2fde24e8292ca44d32728", size = 189015, upload-time = "2026-04-24T19:23:32.647Z" }, + { url = "https://files.pythonhosted.org/packages/98/cd/33230927a780e1398b857e3944abb914556994d252b1d765ae40d112cb25/simplejson-4.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:76fe296ca1df23d290033f10aaacf534fd1b3e3007e7f9ff8aa68b21413aaa78", size = 196658, upload-time = "2026-04-24T19:23:34.563Z" }, + { url = "https://files.pythonhosted.org/packages/cd/84/2c5a7444eb53e9a86d3738299bffddd9f53aeed799ded2f45368221fdb19/simplejson-4.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f0ad25b7dc4e0fb23858355819f2e994f1a5badcdcde8737eac7921c2f1ed2a", size = 185967, upload-time = "2026-04-24T19:23:36.191Z" }, + { url = "https://files.pythonhosted.org/packages/d3/68/454378e06d059cd412a7ed5d87fb6d29fd5b60f13a4d89fc1f764ff434df/simplejson-4.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a59ebd0533f03fd06ff0c42ba0f02d93cbcdd7944922bf3b93911327a95b901f", size = 193940, upload-time = "2026-04-24T19:23:38.151Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d5/a15bf915f623a2c5a079d6e3be8256fdb8ef06f110669493a09b9d6933e0/simplejson-4.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bccbf4419676b517939852e5aeff2af6aee4dc046881c67a1581fa6f1cb01abd", size = 189795, upload-time = "2026-04-24T19:23:40.139Z" }, + { url = "https://files.pythonhosted.org/packages/d2/c9/37212ae7dc4b607f0978c408e8633f05c810884e054c33113184c6c2c8a2/simplejson-4.1.1-cp313-cp313-win32.whl", hash = "sha256:6c845363eb5fd166fb7c72243da38f4fcfde666ede7fdf2cc6fd7762894626f7", size = 88773, upload-time = "2026-04-24T19:23:41.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/c7a0a47883a9015b54c9d8a4b62f2aba17bd4335b1787b9b8a0fc2fa6d52/simplejson-4.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:104d8324c34f25b4b90800bc5fa363780cbc3d8496aef061cba7ce1af9162270", size = 90888, upload-time = "2026-04-24T19:23:43.11Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/4a118a6a92eb33bb08c8e2fe7ec85cb96f0673491bb2b829930831ee4fbe/simplejson-4.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ed7473602b6625de793b6acba49aa949f144a475f538792067e4cf2fda2071f5", size = 110492, upload-time = "2026-04-24T19:23:44.957Z" }, + { url = "https://files.pythonhosted.org/packages/07/f4/84d160e9fa8cada1e0a9381cae4fa81eecd573577a5b34366d8ced59bdf7/simplejson-4.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:225c9caa324c5b554d009fb9cac22aee7711e71bd96f487938c659af467e828e", size = 90152, upload-time = "2026-04-24T19:23:46.355Z" }, + { url = "https://files.pythonhosted.org/packages/68/31/9a5432c433a7671107182cdc9a20ea78a70f99c4e5334aa54b6d4d0d79ed/simplejson-4.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:95407269340c7f22f09776ea7b717a52cf56cfcf119b5e45f66faa4a26445bea", size = 90115, upload-time = "2026-04-24T19:23:47.743Z" }, + { url = "https://files.pythonhosted.org/packages/78/91/3635cdb13318cb0a328abaa69e2b91251caad39d6779aa308098f341f6cb/simplejson-4.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3851658d642c1184d2023f0e6c9ce44a21eb1629e74e7c84ef956b128841fe12", size = 184036, upload-time = "2026-04-24T19:23:49.472Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/149b6ec5393f6849d98c59cadba888b710a8ef4b805ab91e11a566960d40/simplejson-4.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:95a3bb0f78e85f4937f99092239f2011ce06f0f2d803df5c299cc05abbeae008", size = 180543, upload-time = "2026-04-24T19:23:51.023Z" }, + { url = "https://files.pythonhosted.org/packages/df/7c/a5d968d0b527a748b667e62bea94309ccbcb1e2b108e8f0cf8547efaa12b/simplejson-4.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbfdaa7c0603f75b7b14b211b7f2be44696d4e26833ad2d91d5c87bf5fb9a920", size = 188725, upload-time = "2026-04-24T19:23:52.995Z" }, + { url = "https://files.pythonhosted.org/packages/db/e3/6a8d11181d587ef00e2db9112357e6832111e56dd56b01b5c11758a1965d/simplejson-4.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39e3c584071dced8c21b4689f0254303521daeb9b5bc1f4289755d71fa3cb0d3", size = 177492, upload-time = "2026-04-24T19:23:54.581Z" }, + { url = "https://files.pythonhosted.org/packages/67/e3/8b0eb8b06e8198cfbd1270487da163d0093df05cc4f557350cd65e2f7e79/simplejson-4.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:036a27bd0469b9d79557cbddb392969f876cd7f278cfbd0fba81534927a06575", size = 185281, upload-time = "2026-04-24T19:23:56.13Z" }, + { url = "https://files.pythonhosted.org/packages/dc/5f/64990f07ec9e2cb1a814c674e2e21b5693207f74ac70eb72151b847ea4e6/simplejson-4.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b70bfd2f67f3351baba08aa3ae9233c83f21fd95ae5e6b3d0ecb8c647929112f", size = 181848, upload-time = "2026-04-24T19:23:57.92Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/bbc1bc0447f339f79f99ab8c37f7f037cb2f1f93af75d6a4d553096bb0c3/simplejson-4.1.1-cp314-cp314-win32.whl", hash = "sha256:37233c72ce88d06acb92747347742b3c07871eba6789f060c179c9302dde8efe", size = 88761, upload-time = "2026-04-24T19:23:59.397Z" }, + { url = "https://files.pythonhosted.org/packages/18/72/ec1b5cbdcb140c132e6c7bdf99bd73e4f675439e77126c88f472fcffa09c/simplejson-4.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:cc0442dea71cd9cbf30a0b8b9929ab5aa6c02c0443a3d977351e6ec5bada4388", size = 91018, upload-time = "2026-04-24T19:24:00.85Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/4fa437f68ff72219bac3bf3d050de9c6265691f3a170e16954bd69d7cddd/simplejson-4.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c996a4d38290c515af347740659ce095b425449c164a5c9fa3977caa6eff5dbe", size = 113919, upload-time = "2026-04-24T19:24:02.287Z" }, + { url = "https://files.pythonhosted.org/packages/c2/83/59de041d09eb4a9577f7015d7263c32095dfb7fde49717dff62145d89809/simplejson-4.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c65c763fb20d7ca113c1c14dce2fc04a0fc3a57aceff533d6fdac707c7bffb40", size = 91904, upload-time = "2026-04-24T19:24:03.812Z" }, + { url = "https://files.pythonhosted.org/packages/03/8e/46bb345d540f6eb31427d984a4e518cdb182d0621814fee4fee045e8815b/simplejson-4.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0da5c9f57206ee7ef280ff7f1d924937b0a64f9a271a5ef371a2ecdbebba7421", size = 91752, upload-time = "2026-04-24T19:24:05.622Z" }, + { url = "https://files.pythonhosted.org/packages/83/e2/1b2ce97f068835eb3d253c116a4df7a3f436b7bf2fb5ff1ba29287e8b0ec/simplejson-4.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ea3426e786425d10e9e82f8a6eda74a7d6eb10d99165ac3d0d3bbcb65c0ea343", size = 214021, upload-time = "2026-04-24T19:24:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/48/70/d93e556df6a0786298644a7c08304fcbeddc248325f23f38acbebeb21165/simplejson-4.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d75cea7a1025edd7e439b2966b3d977c45b5b899e2adaf422811b3ac702ed9fb", size = 213530, upload-time = "2026-04-24T19:24:09.289Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/c93bf305b9f00d7259e09e713d60e75bd0f7f53da970f716ab90491770e7/simplejson-4.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63c2ada8e58f266491f19eed2eeeb7c25c6141e52f8f9e820f6bb94156cf8dbc", size = 218282, upload-time = "2026-04-24T19:24:10.991Z" }, + { url = "https://files.pythonhosted.org/packages/0c/20/a9b5d2e27ec44b069ee251bd55544fc76929a067107b1050001566ba86f3/simplejson-4.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d1fffb56305c5b475ee746cf9e04f97423ba5aaacd292dc1255bd75b1d3b124b", size = 209249, upload-time = "2026-04-24T19:24:12.662Z" }, + { url = "https://files.pythonhosted.org/packages/97/e4/e06ee682ed5df67592181f5ecb062e35878967e27f5b6e087237d4548d95/simplejson-4.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a6525ec733f43d0541206cffa64fd2aad5a7ae3eb76566aff49cd4db6382209a", size = 213963, upload-time = "2026-04-24T19:24:14.302Z" }, + { url = "https://files.pythonhosted.org/packages/9c/9f/1e160e4cd8cdbf062bf6a454cdf814dc7a48eb47e566fdb8f80ccb202605/simplejson-4.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:861e393260508efa64d8805a8e49c416c3484907e3f146ce966c69552b49b9a3", size = 210474, upload-time = "2026-04-24T19:24:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e6/cecd913df322df5bbe7ebb8ba39e0708e505a165553900da8a7761026d6f/simplejson-4.1.1-cp314-cp314t-win32.whl", hash = "sha256:d083b89d30948a751d3d97476c2ed91e4caaa24a1a1459bdbadb8876242c71fe", size = 91134, upload-time = "2026-04-24T19:24:17.635Z" }, + { url = "https://files.pythonhosted.org/packages/97/73/f540dde99cc1d393bd062ab3b5735b777561a5d8f8a5f2e241164444d77a/simplejson-4.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4cbb299d0528ec0447fe366d8c9641860e28f997a62730690fef905f1f41046e", size = 94467, upload-time = "2026-04-24T19:24:19.109Z" }, + { url = "https://files.pythonhosted.org/packages/ce/6a/8b74c52ffd33dbbde00fe7251fee6a0acdc8cea33f7a43805aed258fb79b/simplejson-4.1.1-py3-none-any.whl", hash = "sha256:2ce92b3748f02423e26d2bfb636fb9d7a8f67c8f5854dcae69d350d123b2eee2", size = 69195, upload-time = "2026-04-24T19:24:57.962Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/68/a1bfbf38c0f7bccc9b10bbf76b94606f64acb1552ae394f0b8285bfaea25/sphinx_rtd_theme-3.1.0.tar.gz", hash = "sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c", size = 7620915, upload-time = "2026-01-12T16:03:31.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/c7/b5c8015d823bfda1a346adb2c634a2101d50bb75d421eb6dcb31acd25ebc/sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl", hash = "sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89", size = 7655617, upload-time = "2026-01-12T16:03:28.101Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "stevedore" +version = "5.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6d/90764092216fa560f6587f83bb70113a8ba510ba436c6476a2b47359057c/stevedore-5.7.0.tar.gz", hash = "sha256:31dd6fe6b3cbe921e21dcefabc9a5f1cf848cf538a1f27543721b8ca09948aa3", size = 516200, upload-time = "2026-02-20T13:27:06.765Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/06/36d260a695f383345ab5bbc3fd447249594ae2fa8dfd19c533d5ae23f46b/stevedore-5.7.0-py3-none-any.whl", hash = "sha256:fd25efbb32f1abb4c9e502f385f0018632baac11f9ee5d1b70f88cc5e22ad4ed", size = 54483, upload-time = "2026-02-20T13:27:05.561Z" }, +] + +[[package]] +name = "twisted" +version = "25.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "automat" }, + { name = "constantly" }, + { name = "hyperlink" }, + { name = "incremental" }, + { name = "typing-extensions" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/0f/82716ed849bf7ea4984c21385597c949944f0f9b428b5710f79d0afc084d/twisted-25.5.0.tar.gz", hash = "sha256:1deb272358cb6be1e3e8fc6f9c8b36f78eb0fa7c2233d2dbe11ec6fee04ea316", size = 3545725, upload-time = "2025-06-07T09:52:24.858Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/66/ab7efd8941f0bc7b2bd555b0f0471bff77df4c88e0cc31120c82737fec77/twisted-25.5.0-py3-none-any.whl", hash = "sha256:8559f654d01a54a8c3efe66d533d43f383531ebf8d81d9f9ab4769d91ca15df7", size = 3204767, upload-time = "2025-06-07T09:52:21.428Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, +] + +[[package]] +name = "yabgp" +source = { editable = "." } +dependencies = [ + { name = "flask" }, + { name = "flask-httpauth" }, + { name = "netaddr" }, + { name = "oslo-config" }, + { name = "py-radix" }, + { name = "simplejson" }, + { name = "twisted" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pyright" }, + { name = "ruff" }, +] +docs = [ + { name = "sphinx" }, + { name = "sphinx-rtd-theme" }, +] +test = [ + { name = "coverage" }, + { name = "pytest" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "flask", specifier = "==3.1.3" }, + { name = "flask-httpauth", specifier = "==4.8.1" }, + { name = "netaddr", specifier = "==1.3.0" }, + { name = "oslo-config", specifier = "==10.3.0" }, + { name = "py-radix", specifier = "==1.1.0" }, + { name = "simplejson", specifier = "==4.1.1" }, + { name = "twisted", specifier = "==25.5.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pyright", specifier = ">=1.1.402" }, + { name = "ruff", specifier = ">=0.11.10" }, +] +docs = [ + { name = "sphinx", specifier = ">=8.0" }, + { name = "sphinx-rtd-theme", specifier = ">=3.0" }, +] +test = [ + { name = "coverage", specifier = ">=7.0" }, + { name = "pytest", specifier = ">=8.0" }, + { name = "pytest-cov", specifier = ">=5.0" }, +] + +[[package]] +name = "zope-interface" +version = "8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/65/34a6e6e4dfa260c4c55ee02bb2fc53625e126ff0181485286cf0c9d453d6/zope_interface-8.4.tar.gz", hash = "sha256:9dbee7925a23aa6349738892c911019d4095a96cff487b743482073ecbc174a8", size = 257736, upload-time = "2026-04-25T07:22:10.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/6a/a08c62bc1fa0e34fe7b8b401646cba4817427c716bfbef6cc88937cd327f/zope_interface-8.4-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:cd55965d715413038774aead54851bc3dbdd74a69f3ce30252182a94407b9905", size = 211924, upload-time = "2026-04-25T07:28:22.219Z" }, + { url = "https://files.pythonhosted.org/packages/50/30/2011f17e00ff078658bc317e1f7eccd7843fc1ce60695b665b0a52c45c1b/zope_interface-8.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0d88c1f106a4f06e074a3ada2d20f4a602e3f2871c4f55726ed5d91e94ec19b1", size = 211995, upload-time = "2026-04-25T07:28:24.107Z" }, + { url = "https://files.pythonhosted.org/packages/25/f3/a16fe884571cfa89271412dbb40def6d6865824428d1e14785a82795100c/zope_interface-8.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:36c575356732d59ffd3279ad67e302a6fe517e67db5b061b36b377ee0fa016c4", size = 264443, upload-time = "2026-04-25T07:28:26.401Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/e08923fcd8a8c8704af05a90418b07cd897ac90865925b37d7ad8139adfa/zope_interface-8.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:29f09ec8bda65f7b30294328070070a2590b90f252f834ee0817cdb0e2c35f6a", size = 269626, upload-time = "2026-04-25T07:28:28.423Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/96c94cd307f9946d0b0f03402a335f7aae7b4f0b129b5734cc56cc78cb65/zope_interface-8.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2bc388cebcb753d21eaf2a0481fd6f0ce6840a47300a40dcec0b56bac27d0f97", size = 269583, upload-time = "2026-04-25T07:28:30.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d4/7e9fcc8bb0dba5d023b9fca92035d68c018457cc550e9d51746670b76a6b/zope_interface-8.4-cp313-cp313-win_amd64.whl", hash = "sha256:3e5866917ccb57d929e515a1136d729bd3fa4f367965fb16e38a4bc72cb05521", size = 214422, upload-time = "2026-04-25T07:28:32.201Z" }, + { url = "https://files.pythonhosted.org/packages/16/26/b0bcde302f6a4c155d047a8ab5cba1003363031919d6e8f3bcdc139c28a6/zope_interface-8.4-cp313-cp313-win_arm64.whl", hash = "sha256:f1f854bef8bc137519e4413bcc1322d55faad28b20b3ca39f7bec49d2f1b26df", size = 213029, upload-time = "2026-04-25T07:28:34.677Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d5/ca60c8b404b303d9490e1417430a5198a77557dbeb17c1cb31616e432318/zope_interface-8.4-cp314-cp314-macosx_10_9_x86_64.whl", hash = "sha256:7cbb887fdbfaacb4c362dbb487033551646e28013ad5ffe72e96eb260003a1a1", size = 212012, upload-time = "2026-04-25T07:28:36.88Z" }, + { url = "https://files.pythonhosted.org/packages/83/64/6bb9f54250c817e24b39e986f173b6cd21ff658bec6c6cc0baad05d761e4/zope_interface-8.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a5638c6be715116d3453e6d099c299c6844d54810de7445ce116424e905ede06", size = 212071, upload-time = "2026-04-25T07:28:38.742Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cf/42851262e102723058019dc7d0b48210b85a935f79ae32ce60ddccc2e8fb/zope_interface-8.4-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b8147b40bfcd53803870a9519e0879ff066aeecc2fcff8295663c1b17fc38dc2", size = 266075, upload-time = "2026-04-25T07:28:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a7/e48c79b836f6f0a2c219288e2ec343517f90e95c93de5435a8a23918bf20/zope_interface-8.4-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:049ba3c7b38cc400ae08e011617635706e0f442e1d075db1b015246fcbf6091e", size = 269127, upload-time = "2026-04-25T07:28:42.868Z" }, + { url = "https://files.pythonhosted.org/packages/6a/40/0e26f24d3a2f34f0de2cfeaab6458a865284d9d1fa317ab78913aa1f7322/zope_interface-8.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9c4ac009c2c8e43283842f80387c4d4b41bcbc293391c3b9ab71532ae1ccc301", size = 269446, upload-time = "2026-04-25T07:28:44.97Z" }, + { url = "https://files.pythonhosted.org/packages/91/d5/20310601450367fc35fa28b0544c98d0347b8cc25eaf106a2c4cc36841e1/zope_interface-8.4-cp314-cp314-win_amd64.whl", hash = "sha256:4713bf651ec36e7eea49d2ace4f0e89bec2b33a339674874b1121f2537edc62a", size = 215199, upload-time = "2026-04-25T07:28:47.146Z" }, + { url = "https://files.pythonhosted.org/packages/5b/00/0d22ce75126e31f81baa5889e2a40aad37c8e34d1220cf8b18d744f2b5d9/zope_interface-8.4-cp314-cp314-win_arm64.whl", hash = "sha256:d934497c4b72d5f528d2b5ebe9b8b5a7004b5877948ebd4ea00c2432fb27178f", size = 213178, upload-time = "2026-04-25T07:28:48.868Z" }, +] diff --git a/yabgp/__init__.py b/yabgp/__init__.py index 71d2401f..eddbd9a8 100644 --- a/yabgp/__init__.py +++ b/yabgp/__init__.py @@ -15,4 +15,5 @@ """version information""" -version = '0.9.0.dev0' +__version__ = '1.0' +version = __version__ diff --git a/yabgp/agent/__init__.py b/yabgp/agent/__init__.py index caa0473d..bd712882 100644 --- a/yabgp/agent/__init__.py +++ b/yabgp/agent/__init__.py @@ -13,9 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. -import sys -import os import logging +import os +import sys import traceback from oslo_config import cfg @@ -23,14 +23,13 @@ from twisted.web.server import Site from twisted.web.wsgi import WSGIResource -from yabgp import version, log -from yabgp.core.factory import BGPPeering -from yabgp.config import get_bgp_config -from yabgp.common import constants as bgp_cons +from yabgp import log, version from yabgp.api.app import app +from yabgp.common import constants as bgp_cons +from yabgp.config import get_bgp_config +from yabgp.core.factory import BGPPeering from yabgp.handler.default_handler import DefaultHandler - log.early_init_log(logging.DEBUG) CONF = cfg.CONF diff --git a/yabgp/agent/cmd.py b/yabgp/agent/cmd.py index 3db08c28..32c0bd13 100644 --- a/yabgp/agent/cmd.py +++ b/yabgp/agent/cmd.py @@ -15,7 +15,6 @@ """start service""" -from __future__ import print_function from yabgp.agent import prepare_service diff --git a/yabgp/api/app.py b/yabgp/api/app.py index f3fd56d4..69e316aa 100644 --- a/yabgp/api/app.py +++ b/yabgp/api/app.py @@ -17,11 +17,10 @@ """ import flask - -from yabgp.api import v1 -from yabgp.api import config from oslo_config import cfg +from yabgp.api import config, v1 + app = flask.Flask('yabgp.api') app.config['SECRET_KEY'] = 'cisco123' app.register_blueprint(v1.blueprint, url_prefix='/v1') @@ -81,4 +80,4 @@ def version_descriptor(base_url, version): def version_url(base_url, version_number): - return '%s/%s' % (base_url, version_number) + return f'{base_url}/{version_number}' diff --git a/yabgp/api/config.py b/yabgp/api/config.py index 5b476d72..ba12a1b5 100644 --- a/yabgp/api/config.py +++ b/yabgp/api/config.py @@ -15,7 +15,6 @@ from oslo_config import cfg - CONF = cfg.CONF rest_server_ops = [ diff --git a/yabgp/api/utils.py b/yabgp/api/utils.py index 7e518324..90290a11 100644 --- a/yabgp/api/utils.py +++ b/yabgp/api/utils.py @@ -13,13 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. -import time import logging +import time from functools import wraps -from oslo_config import cfg -from flask import request import flask +from flask import request +from oslo_config import cfg from yabgp.common import constants as common_cons @@ -136,7 +136,7 @@ def send_route_refresh(peer_ip, afi, safi, res): else: return { 'status': False, - 'code': 'address family unsupported, afi=%s,safi=%s' % (afi, safi) + 'code': f'address family unsupported, afi={afi},safi={safi}' } except Exception as e: LOG.error(e) diff --git a/yabgp/api/v1.py b/yabgp/api/v1.py index 802f9c8b..99c1bc71 100644 --- a/yabgp/api/v1.py +++ b/yabgp/api/v1.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding:utf-8 -*- # Copyright 2015 Cisco Systems, Inc. # All rights reserved. @@ -20,12 +19,12 @@ """ import binascii import logging -import time import re +import time -from flask_httpauth import HTTPBasicAuth -from flask import Blueprint, request import flask +from flask import Blueprint, request +from flask_httpauth import HTTPBasicAuth from oslo_config import cfg from yabgp.api import utils as api_utils @@ -218,7 +217,7 @@ def send_update_message(peer_ip): else: return flask.jsonify({ 'status': False, - 'code': 'unexpected extended community "%s", please check your post data' % key + 'code': f'unexpected extended community "{key}", please check your post data' }) attr[16] = ext_community if cfg.CONF.bgp.rib: @@ -394,7 +393,7 @@ def json_to_bin(peer_ip): else: return flask.jsonify({ 'status': False, - 'code': 'unexpected extended community "%s", please check your post data' % key + 'code': f'unexpected extended community "{key}", please check your post data' }) attr[16] = ext_community if (attr and nlri) or withdraw: diff --git a/yabgp/common/constants.py b/yabgp/common/constants.py index 7a379559..ce631026 100644 --- a/yabgp/common/constants.py +++ b/yabgp/common/constants.py @@ -528,7 +528,7 @@ } WELL_KNOW_COMMUNITY_STR_2_INT = dict( - [(r, l) for (l, r) in WELL_KNOW_COMMUNITY_INT_2_STR.items()]) + [(r, label) for (label, r) in WELL_KNOW_COMMUNITY_INT_2_STR.items()]) TCP_MD5SIG_MAXKEYLEN = 80 SS_PADSIZE_IPV4 = 120 diff --git a/yabgp/common/exception.py b/yabgp/common/exception.py index 1bd83c49..fd6a75ea 100644 --- a/yabgp/common/exception.py +++ b/yabgp/common/exception.py @@ -25,14 +25,14 @@ class BGPException(Exception): def __init__(self, **kwargs): try: - super(BGPException, self).__init__(self.message % kwargs) + super().__init__(self.message % kwargs) self.msg = self.message % kwargs except Exception: if _FATAL_EXCEPTION_FORMAT_ERRORS: raise else: # at least get the core message out if something happened - super(BGPException, self).__init__(self.message) + super().__init__(self.message) def __unicode__(self): return self.msg.decode('utf8', 'ignore') @@ -50,13 +50,13 @@ def __init__(self, sub_error, data=''): self.msg = self.message % {'sub_error': sub_error, 'data': data} self.sub_error = sub_error self.data = data - super(NotificationSent, self).__init__(self.msg) + super().__init__(self.msg) except Exception: if _FATAL_EXCEPTION_FORMAT_ERRORS: raise else: # at least get the core message out if something happened - super(NotificationSent, self).__init__(self.message) + super().__init__(self.message) def __unicode__(self): return self.msg.decode('utf8', 'ignore') @@ -78,7 +78,7 @@ class UpdateMessageError(NotificationSent): message = "BGP Update Message Error, sub error:%(sub_error)s, data:%(data)s" def __init__(self, sub_error, data='', sub_results=None): - super(UpdateMessageError, self).__init__(sub_error, data) + super().__init__(sub_error, data) self.sub_results = sub_results diff --git a/yabgp/common/tlv.py b/yabgp/common/tlv.py index fb93200a..a58634e3 100644 --- a/yabgp/common/tlv.py +++ b/yabgp/common/tlv.py @@ -16,7 +16,7 @@ import binascii -class TLV(object): +class TLV: """TLV basic class """ TYPE = -1 @@ -26,7 +26,7 @@ def __init__(self, value): self.value = value def __str__(self): - return '%s: %s' % (self.TYPE_STR, self.value) + return f'{self.TYPE_STR}: {self.value}' @classmethod def parse(cls, value, typecode=-1): diff --git a/yabgp/config.py b/yabgp/config.py index 09c0dd93..8c287e8d 100644 --- a/yabgp/config.py +++ b/yabgp/config.py @@ -20,8 +20,7 @@ from oslo_config import cfg -from yabgp.common.constants import AFI_SAFI_STR_DICT -from yabgp.common.constants import AFI_STR_DICT +from yabgp.common.constants import AFI_SAFI_STR_DICT, AFI_STR_DICT CONF = cfg.CONF diff --git a/yabgp/core/factory.py b/yabgp/core/factory.py index b0167ad3..c239dcd2 100644 --- a/yabgp/core/factory.py +++ b/yabgp/core/factory.py @@ -17,20 +17,19 @@ """ import logging -import socket import platform +import socket import struct import sys import netaddr -from twisted.internet import protocol -from twisted.internet import reactor from oslo_config import cfg +from twisted.internet import protocol, reactor -from yabgp.core.protocol import BGP -from yabgp.core.fsm import FSM from yabgp.common import constants as bgp_cons from yabgp.common.afn import AFNUM_INET +from yabgp.core.fsm import FSM +from yabgp.core.protocol import BGP LOG = logging.getLogger(__name__) @@ -101,7 +100,6 @@ def __init__(self, myasn=None, myaddr=None, peerasn=None, peeraddr=None, self.estab_protocol = None def buildProtocol(self, addr): - """Builds a BGP protocol instance :param addr: IP address used for building protocol. @@ -115,7 +113,6 @@ def buildProtocol(self, addr): return p def _initProtocol(self, protocol, addr): - """Initializes a BGPProtocol instance :param protocol: twisted Protocol @@ -134,14 +131,13 @@ def _initProtocol(self, protocol, addr): protocol.fsm.state = bgp_cons.ST_ACTIVE def clientConnectionFailed(self, connector, reason): - """Called when the outgoing connection failed. :param connector: Twisted connector :param reason: connection failed reason """ - error_msg = "[%s]Client connection failed: %s" % (self.peer_addr, reason.getErrorMessage()) + error_msg = f"[{self.peer_addr}]Client connection failed: {reason.getErrorMessage()}" self.handler.on_connection_failed(self.peer_addr, reason.getErrorMessage()) LOG.info(error_msg) # There is no protocol instance yet at this point. @@ -152,7 +148,6 @@ def clientConnectionFailed(self, connector, reason): LOG.info("[%s]Client connection failed: %s", self.peer_addr, e) def automatic_start(self, idle_hold=False): - """BGP AutomaticStart event (event 3) :param idle_hold: flag represents used Idle Hold @@ -175,7 +170,6 @@ def manual_start(self, idle_hold=False): return False def manual_stop(self): - """BGP ManualStop event (event 2) Returns a DeferredList that will fire once the connection(s) have closed""" @@ -202,7 +196,6 @@ def connection_closed(self, pro, disconnect=False): self.automatic_start(idle_hold=True) def connect_retry(self): - """Called by FSM when we should reattempt to connect. """ try: @@ -231,7 +224,6 @@ def set_peer_id(self, bgp_id): self.peer_id = None def connect(self): - """Initiates a TCP client connection to the peer. Should only be called from BGPPeering or FSM, otherwise use manualStart() instead. """ @@ -281,13 +273,12 @@ def get_tcp_md5sig(md5_str, host, port): n_port = socket.htons(port) if afi == AFNUM_INET: n_addr = socket.inet_pton(socket.AF_INET, host) - tcp_md5sig = 'HH4s%dx2xH4x%ds' % ( - bgp_cons.SS_PADSIZE_IPV4, bgp_cons.TCP_MD5SIG_MAXKEYLEN) + tcp_md5sig = f'HH4s{bgp_cons.SS_PADSIZE_IPV4}x2xH4x{bgp_cons.TCP_MD5SIG_MAXKEYLEN}s' md5sig = struct.pack( tcp_md5sig, socket.AF_INET, n_port, n_addr, len(md5_str), md5_str.encode()) return md5sig else: return None - except socket.error as e: + except OSError as e: LOG.error('This linux machine does not support TCP_MD5SIG: (%s)', str(e)) return None diff --git a/yabgp/core/fsm.py b/yabgp/core/fsm.py index e57b9be0..a601c3a1 100644 --- a/yabgp/core/fsm.py +++ b/yabgp/core/fsm.py @@ -14,19 +14,20 @@ """BGP FMS Implementation""" -import time import logging +import time from oslo_config import cfg -from yabgp.core.timer import BGPTimer + from yabgp.common import constants as bgp_cons +from yabgp.core.timer import BGPTimer CONF = cfg.CONF LOG = logging.getLogger(__name__) -class FSM(object): +class FSM: """ Implements BGP Events described in section 8.1 of RFC 4271 @@ -79,7 +80,7 @@ def __setattr__(self, name, value): if value == bgp_cons.ST_ESTABLISHED: self.uptime = time.time() self.bgp_peering.handler.on_established(peer=self.bgp_peering.peer_addr, msg=self.uptime) - super(FSM, self).__setattr__(name, value) + super().__setattr__(name, value) def manual_start(self, idle_hold=False): diff --git a/yabgp/core/protocol.py b/yabgp/core/protocol.py index a7b5a7ea..58c51870 100644 --- a/yabgp/core/protocol.py +++ b/yabgp/core/protocol.py @@ -15,25 +15,24 @@ """BGP Protocol""" +import copy import logging -import traceback import struct import time +import traceback import netaddr from oslo_config import cfg -from twisted.internet import protocol -from twisted.internet import reactor from radix import Radix -import copy +from twisted.internet import protocol, reactor from yabgp.common import constants as bgp_cons -from yabgp.message.open import Open +from yabgp.common import exception as excep from yabgp.message.keepalive import KeepAlive -from yabgp.message.update import Update from yabgp.message.notification import Notification +from yabgp.message.open import Open from yabgp.message.route_refresh import RouteRefresh -from yabgp.common import exception as excep +from yabgp.message.update import Update LOG = logging.getLogger(__name__) @@ -44,7 +43,6 @@ class BGP(protocol.Protocol): """Protocol class for BGP 4""" def __init__(self): - """Create a BGP protocol. """ self.fsm = None @@ -105,7 +103,6 @@ def init_rib(self): self.adj_rib_out = {k: {} for k in CONF.bgp.afi_safi} def connectionMade(self): - """ Starts the initial negotiation of the protocol """ @@ -260,7 +257,6 @@ def _update_received(self, timestamp, msg): # if self.msg_recv_stat['Updates'] % 1000 == 0: # LOG.info(self.msg_recv_stat['Updates']) # LOG.info(time.time()) - """Called when a BGP Update message was received.""" # TODO: Need to convert `self.add_path_ipv4_receive` and `self.add_path_ipv4_send` into a unified # `afi_add_path` format. @@ -573,7 +569,6 @@ def _route_refresh_received(self, msg, msg_type): self.handler.route_refresh_received(self, nofi_msg, msg_type) def negotiate_hold_time(self, hold_time): - """Negotiates the hold time""" self.fsm.hold_time = min(self.fsm.hold_time, hold_time) @@ -731,7 +726,7 @@ def update_send_version(self, peer_ip, attr, nlri, withdraw): self.send_version['flowspec'] += 1 del self.flowspec_send_dict[str(key)] else: - LOG.info("Do not have %s in send flowspec dict" % key) + LOG.info(f"Do not have {key} in send flowspec dict") elif attr[15]['afi_safi'] == [1, 73]: LOG.info('withdraw sr') key = "{" @@ -746,7 +741,7 @@ def update_send_version(self, peer_ip, attr, nlri, withdraw): self.send_version['sr_policy'] += 1 del self.sr_send_dict[str(key)] else: - LOG.info("Do not have %s in send flowspec dict" % key) + LOG.info(f"Do not have {key} in send flowspec dict") elif attr[15]['afi_safi'] == [1, 128]: LOG.info("withdraw mpls_vpn") for prefix in attr[15]['withdraw']: @@ -762,7 +757,7 @@ def update_send_version(self, peer_ip, attr, nlri, withdraw): self.send_version['mpls_vpn'] += 1 del self.mpls_vpn_send_dict[str(key)] else: - LOG.info("Do not have %s in send flowspec dict" % key) + LOG.info(f"Do not have {key} in send flowspec dict") def update_receive_verion(self, attr, nlri, withdraw): if 14 in attr: @@ -831,7 +826,7 @@ def update_receive_verion(self, attr, nlri, withdraw): self.receive_version['flowspec'] += 1 del self.flowspec_receive_dict[str(key)] else: - LOG.info("Do not have %s in receive flowspec dict" % prefix) + LOG.info(f"Do not have {prefix} in receive flowspec dict") elif attr[15]['afi_safi'] == [1, 73]: LOG.info('recieve sr withdraw') elif attr[15]['afi_safi'] == [1, 128]: @@ -849,7 +844,7 @@ def update_receive_verion(self, attr, nlri, withdraw): self.receive_version['mpls_vpn'] += 1 del self.mpls_vpn_receive_dict[str(key)] else: - LOG.info("Do not have %s in receive mpls_vpn dict" % key) + LOG.info(f"Do not have {key} in receive mpls_vpn dict") def compare_add_path(self, local_add_path, remote_add_path): if not local_add_path or not remote_add_path: diff --git a/yabgp/core/timer.py b/yabgp/core/timer.py index 5ed6394f..30282258 100644 --- a/yabgp/core/timer.py +++ b/yabgp/core/timer.py @@ -15,10 +15,10 @@ """ BGP Timer""" # Twisted modules -from twisted.internet import reactor, error +from twisted.internet import error, reactor -class BGPTimer(object): +class BGPTimer: """ Timer class with a slightly different Timer interface than the Twisted DelayedCall interface diff --git a/yabgp/handler/__init__.py b/yabgp/handler/__init__.py index dc6984cf..80b68cac 100644 --- a/yabgp/handler/__init__.py +++ b/yabgp/handler/__init__.py @@ -1,15 +1,12 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- import abc import logging from queue import Queue - LOG = logging.getLogger(__name__) -class BaseHandler(object): - __metaclass__ = abc.ABCMeta +class BaseHandler(metaclass=abc.ABCMeta): def __init__(self): """ @@ -22,44 +19,44 @@ def __init__(self): @abc.abstractmethod def init(self): - raise NotImplemented + raise NotImplementedError @abc.abstractmethod def on_update_error(self, peer, timestamp, msg): - raise NotImplemented + raise NotImplementedError @abc.abstractmethod def update_received(self, peer, timestamp, msg): - raise NotImplemented + raise NotImplementedError @abc.abstractmethod def keepalive_received(self, peer, timestamp): - raise NotImplemented + raise NotImplementedError @abc.abstractmethod def open_received(self, peer, timestamp, result): - raise NotImplemented + raise NotImplementedError @abc.abstractmethod def send_open(self, peer, timestamp, result): - raise NotImplemented + raise NotImplementedError @abc.abstractmethod def route_refresh_received(self, peer, msg, msg_type): - raise NotImplemented + raise NotImplementedError @abc.abstractmethod def notification_received(self, peer, msg): - raise NotImplemented + raise NotImplementedError @abc.abstractmethod def on_connection_lost(self, peer): - raise NotImplemented + raise NotImplementedError @abc.abstractmethod def on_connection_failed(self, peer, msg): - raise NotImplemented + raise NotImplementedError @abc.abstractmethod def on_established(self, peer, msg): - raise NotImplemented + raise NotImplementedError diff --git a/yabgp/handler/default_handler.py b/yabgp/handler/default_handler.py index 52880448..c1265925 100644 --- a/yabgp/handler/default_handler.py +++ b/yabgp/handler/default_handler.py @@ -1,16 +1,14 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- # import json # replace with simplejson -import simplejson as json - +import logging import os +import sys import time -import logging import traceback -import sys +import simplejson as json from oslo_config import cfg from yabgp.common import constants as bgp_cons @@ -41,7 +39,7 @@ class DefaultHandler(BaseHandler): def __init__(self): - super(DefaultHandler, self).__init__() + super().__init__() ''' {: (, )} ''' @@ -74,7 +72,7 @@ def init_msg_file(self, peer_addr): last_msg_seq, msg_file_name = DefaultHandler.get_last_seq_and_file(msg_path) if not msg_file_name: - msg_file_name = "%s.msg" % time.time() + msg_file_name = f"{time.time()}.msg" # store the message sequence self.msg_sequence[peer_addr] = last_msg_seq + 1 msg_file = open(os.path.join(msg_path, msg_file_name), 'a') @@ -97,7 +95,7 @@ def get_last_seq_and_file(msg_path): file_list.sort() msg_file_name = file_list[-1] try: - with open(msg_path + msg_file_name, 'r') as fh: + with open(msg_path + msg_file_name) as fh: line = None for line in fh: pass @@ -154,7 +152,7 @@ def check_file_size(self, peer): if msg_path: if os.path.getsize(cur_file.name) >= CONF.message.write_msg_max_size: cur_file.close() - msg_file_name = "%s.msg" % time.time() + msg_file_name = f"{time.time()}.msg" LOG.info('Open a new message file %s', msg_file_name) msg_file = open(os.path.join(msg_path + msg_file_name), 'a') self.peer_files[peer.lower()] = (msg_path, msg_file) diff --git a/yabgp/hooks.py b/yabgp/hooks.py deleted file mode 100644 index bb862842..00000000 --- a/yabgp/hooks.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2015 Cisco Systems, Inc. -# All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -# copy from # get from https://github.com/osrg/ryu/blob/master/ryu/hooks.py -import sys -from setuptools.command import easy_install - -from yabgp import version - - -# Global variables in this module doesn't work as we expect -# because, during the setup procedure, this module seems to be -# copied (as a file) and can be loaded multiple times. -# We save them into __main__ module instead. -def _main_module(): - return sys.modules['__main__'] - - -def save_orig(): - """Save original easy_install.get_script_args. - This is necessary because pbr's setup_hook is sometimes called - before ours.""" - _main_module()._orig_get_script_args = easy_install.get_script_args - - -def setup_hook(config): - """Filter config parsed from a setup.cfg to inject our defaults.""" - metadata = config['metadata'] - requires = metadata.get('requires_dist', '').split('\n') - if sys.platform == 'win32': - requires.append('pywin32') - requires.append('wmi') - metadata['requires_dist'] = "\n".join(requires) - config['metadata'] = metadata - metadata['version'] = str(version) - - # pbr's setup_hook replaces easy_install.get_script_args with - # their own version, override_get_script_args, prefering simpler - # scripts which are not aware of multi-version. - # prevent that by doing the opposite. it's a horrible hack - # but we are in patching wars already... - from pbr import packaging - - def my_get_script_args(*args, **kwargs): - return _main_module()._orig_get_script_args(*args, **kwargs) - - packaging.override_get_script_args = my_get_script_args - easy_install.get_script_args = my_get_script_args - - # another hack to allow setup from tarball. - orig_get_version = packaging.get_version - - def my_get_version(package_name, pre_version=None): - if package_name == 'yabgp': - return str(version) - return orig_get_version(package_name, pre_version) - - packaging.get_version = my_get_version diff --git a/yabgp/log.py b/yabgp/log.py index 18f60666..42ecfe45 100644 --- a/yabgp/log.py +++ b/yabgp/log.py @@ -16,21 +16,16 @@ """ logging handler. Reference from https://github.com/osrg/ryu/blob/master/ryu/log.py """ -from __future__ import print_function import inspect import logging import logging.config import logging.handlers import os import sys -if sys.version_info[0] == 2: - import ConfigParser -elif sys.version_info[0] == 3: - from configparser import ConfigParser +from configparser import ConfigParser from oslo_config import cfg - CONF = cfg.CONF CONF.register_cli_opts([ @@ -93,7 +88,7 @@ def init_log(): for handler in log.handlers: handler.setFormatter(logging.Formatter(DEBUG_LOG_FORMAT)) except ConfigParser.Error as e: - print('Failed to parse %s: %s' % (CONF.log_config_file, e), + print(f'Failed to parse {CONF.log_config_file}: {e}', file=sys.stderr) sys.exit(2) return diff --git a/yabgp/message/attribute/__init__.py b/yabgp/message/attribute/__init__.py index f8b5cdff..ae3edf18 100644 --- a/yabgp/message/attribute/__init__.py +++ b/yabgp/message/attribute/__init__.py @@ -68,7 +68,7 @@ def __str_(self): r.append("OPTIONAL_TRANSITIVE") v -= 0xc0 if v: - r.append("UNKNOWN %s" % hex(v)) + r.append(f"UNKNOWN {hex(v)}") return " ".join(r) @@ -155,10 +155,10 @@ def __str__(self): return "LARGE_COMMUNITY" if self == 0xffff: return "INTERNAL SPLIT" - return 'UNKNOWN ATTRIBUTE (%s)' % hex(self) + return f'UNKNOWN ATTRIBUTE ({hex(self)})' -class Attribute(object): +class Attribute: """ Base class for all BGP attribute classes Attribute instances are (meant to be) immutable once initialized @@ -177,7 +177,7 @@ def _attribute(self, value): len_value = pack('!H', length)[0] else: len_value = chr(length) - return "%s%s%s%s" % (chr(flag), chr(self.ID), len_value, value) + return f"{chr(flag)}{chr(self.ID)}{len_value}{value}" def __eq__(self, other): return self.ID == other.ID diff --git a/yabgp/message/attribute/aggregator.py b/yabgp/message/attribute/aggregator.py index eaa8d210..dfececd0 100644 --- a/yabgp/message/attribute/aggregator.py +++ b/yabgp/message/attribute/aggregator.py @@ -17,11 +17,9 @@ import netaddr -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeID -from yabgp.message.attribute import AttributeFlag from yabgp.common import constants as bgp_cons from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class Aggregator(Attribute): diff --git a/yabgp/message/attribute/aspath.py b/yabgp/message/attribute/aspath.py index 396ac7be..779084c6 100644 --- a/yabgp/message/attribute/aspath.py +++ b/yabgp/message/attribute/aspath.py @@ -15,11 +15,9 @@ import struct -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeID -from yabgp.message.attribute import AttributeFlag -from yabgp.common import exception as excep from yabgp.common import constants as bgp_cons +from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class ASPath(Attribute): @@ -58,11 +56,10 @@ class ASPath(Attribute): @classmethod def parse(cls, value, asn4=False): - """ Parse AS PATH attributes. :param asn4: 4 bytes asn or not - :param value: raw binary balue + :param value: raw binary value """ aspath = [] offset = 0 @@ -101,7 +98,7 @@ def parse(cls, value, asn4=False): sub_error=bgp_cons.ERR_MSG_UPDATE_ATTR_LEN, data='') - fmt = '!%d%s' % (num_ases, asn_fmt_char) + fmt = f'!{num_ases}{asn_fmt_char}' segment = list(struct.unpack_from(fmt, value, offset)) aspath.append((seg_type, segment)) @@ -111,7 +108,6 @@ def parse(cls, value, asn4=False): @classmethod def construct(cls, value, asn4=False): - """ Construct AS PATH. :param asn4: 4byte asn diff --git a/yabgp/message/attribute/atomicaggregate.py b/yabgp/message/attribute/atomicaggregate.py index 8cfe32d7..3f813ae0 100644 --- a/yabgp/message/attribute/atomicaggregate.py +++ b/yabgp/message/attribute/atomicaggregate.py @@ -15,11 +15,9 @@ import struct -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeID -from yabgp.message.attribute import AttributeFlag from yabgp.common import constants as bgp_cons from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class AtomicAggregate(Attribute): diff --git a/yabgp/message/attribute/clusterlist.py b/yabgp/message/attribute/clusterlist.py index df6de39b..74ccd274 100644 --- a/yabgp/message/attribute/clusterlist.py +++ b/yabgp/message/attribute/clusterlist.py @@ -17,11 +17,9 @@ import netaddr -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeID -from yabgp.message.attribute import AttributeFlag from yabgp.common import constants as bgp_cons from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class ClusterList(Attribute): diff --git a/yabgp/message/attribute/community.py b/yabgp/message/attribute/community.py index 9b66711d..b5c5823e 100644 --- a/yabgp/message/attribute/community.py +++ b/yabgp/message/attribute/community.py @@ -15,11 +15,9 @@ import struct -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeID -from yabgp.message.attribute import AttributeFlag -from yabgp.common import exception as excep from yabgp.common import constants as bgp_cons +from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class Community(Attribute): @@ -45,14 +43,14 @@ def parse(cls, value): community = [] if value: try: - length = len(value) / 2 - value_list = list(struct.unpack('!%dH' % length, value)) + length = len(value) // 2 + value_list = list(struct.unpack(f'!{length}H', value)) while value_list: value_type = value_list[0] * 16 * 16 * 16 * 16 + value_list[1] if value_type in bgp_cons.WELL_KNOW_COMMUNITY_INT_2_STR: community.append(bgp_cons.WELL_KNOW_COMMUNITY_INT_2_STR[value_type]) else: - community.append("%s:%s" % (value_list[0], value_list[1])) + community.append(f"{value_list[0]}:{value_list[1]}") value_list = value_list[2:] except Exception: raise excep.UpdateMessageError( diff --git a/yabgp/message/attribute/extcommunity.py b/yabgp/message/attribute/extcommunity.py index 6f6e1f3d..fc6f8974 100644 --- a/yabgp/message/attribute/extcommunity.py +++ b/yabgp/message/attribute/extcommunity.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding:utf-8 -*- # Copyright 2015-2017 Cisco Systems, Inc. # All rights reserved. @@ -19,18 +18,15 @@ """BGP extended community """ -import struct -import logging import binascii -from builtins import range +import logging +import struct import netaddr -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeID -from yabgp.message.attribute import AttributeFlag -from yabgp.common import exception as excep from yabgp.common import constants as bgp_cons +from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID LOG = logging.getLogger() @@ -54,7 +50,6 @@ class ExtCommunity(Attribute): @classmethod def parse(cls, value): - """ Each Extended Community is encoded as an 8-octet quantity, as follows: @@ -87,86 +82,86 @@ def parse(cls, value): if comm_code == bgp_cons.BGP_EXT_COM_RT_0: # Route Target, Format AS(2bytes):AN(4bytes) asn, an = struct.unpack('!HI', value_tmp) - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], asn, an)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{asn}:{an}') elif comm_code == bgp_cons.BGP_EXT_COM_RT_1: # Route Target,Format IPv4 address(4bytes):AN(2bytes) ipv4 = str(netaddr.IPAddress(struct.unpack('!I', value_tmp[0:4])[0])) an = struct.unpack('!H', value_tmp[4:])[0] - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], ipv4, an)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{ipv4}:{an}') elif comm_code == bgp_cons.BGP_EXT_COM_RT_2: # Route Target,Format AS(4bytes):AN(2bytes) asn, an = struct.unpack('!IH', value_tmp) - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], asn, an)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{asn}:{an}') # Route Origin elif comm_code == bgp_cons.BGP_EXT_COM_RO_0: # Route Origin,Format AS(2bytes):AN(4bytes) asn, an = struct.unpack('!HI', value_tmp) - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], asn, an)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{asn}:{an}') elif comm_code == bgp_cons.BGP_EXT_COM_RO_1: # Route Origin,Format IP address:AN(2bytes) ipv4 = str(netaddr.IPAddress(struct.unpack('!I', value_tmp[0:4])[0])) an = struct.unpack('!H', value_tmp[4:])[0] - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], ipv4, an)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{ipv4}:{an}') elif comm_code == bgp_cons.BGP_EXT_COM_RO_2: # Route Origin,Format AS(2bytes):AN(4bytes) asn, an = struct.unpack('!IH', value_tmp) - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], asn, an)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{asn}:{an}') # BGP Flow spec elif comm_code == bgp_cons.BGP_EXT_REDIRECT_NH: ipv4 = str(netaddr.IPAddress(int(binascii.b2a_hex(value_tmp[0:4]), 16))) copy_flag = struct.unpack('!H', value_tmp[4:])[0] - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], ipv4, copy_flag)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{ipv4}:{copy_flag}') elif comm_code == bgp_cons.BGP_EXT_TRA_RATE: asn, rate = struct.unpack('!Hf', value_tmp) - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], asn, int(rate))) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{asn}:{int(rate)}') elif comm_code == bgp_cons.BGP_EXT_TRA_ACTION: bit_value = parse_bit(ord(value_tmp[-1])) ext_community.append( - '%s:S:%s,T:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], bit_value['6'], bit_value['7'])) + '{}:S:{},T:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], bit_value['6'], bit_value['7'])) elif comm_code == bgp_cons.BGP_EXT_REDIRECT_VRF: asn, an = struct.unpack('!HI', value_tmp) - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], asn, an)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{asn}:{an}') elif comm_code == bgp_cons.BGP_EXT_TRA_MARK: mark = ord(value_tmp[-1:]) - ext_community.append('%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], mark)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{mark}') # Transitive Opaque elif comm_code == bgp_cons.BGP_EXT_COM_ENCAP: ext_community.append( - '%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], struct.unpack('!I', value_tmp[2:])[0])) + '{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], struct.unpack('!I', value_tmp[2:])[0])) elif comm_code == bgp_cons.BGP_EXT_COM_COLOR: - ext_community.append('%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], - struct.unpack('!I', value_tmp[2:])[0])) + ext_community.append('{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], + struct.unpack('!I', value_tmp[2:])[0])) # EVPN elif comm_code == bgp_cons.BGP_EXT_COM_EVPN_ES_IMPORT: mac = str(netaddr.EUI(int(binascii.b2a_hex(value_tmp), 16))) - ext_community.append('%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], mac)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{mac}') elif comm_code == bgp_cons.BGP_EXT_COM_EVPN_MAC_MOBIL: flag = ord(value_tmp[0:1]) seq = struct.unpack('!I', value_tmp[2:])[0] - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], flag, seq)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{flag}:{seq}') elif comm_code == bgp_cons.BGP_EXT_COM_EVPN_ESI_MPLS_LABEL: flag = ord(value_tmp[0:1]) label = struct.unpack('!L', b'\00' + value_tmp[3:])[0] label >>= 4 - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], flag, label)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{flag}:{label}') elif comm_code == bgp_cons.BGP_EXT_COM_EVPN_ROUTE_MAC: - ext_community.append('%s:%s' % ( - bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], str(netaddr.EUI(int(binascii.b2a_hex(value_tmp), 16))))) + mac = str(netaddr.EUI(int(binascii.b2a_hex(value_tmp), 16))) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{mac}') # BGP link bandwith elif comm_code == bgp_cons.BGP_EXT_COM_LINK_BW: asn, an = struct.unpack('!HI', value_tmp) - ext_community.append('%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[comm_code], asn, an)) + ext_community.append(f'{bgp_cons.BGP_EXT_COM_STR_DICT[comm_code]}:{asn}:{an}') else: ext_community.append([bgp_cons.BGP_EXT_COM_UNKNOW, repr(value_tmp)]) - LOG.warn('unknow bgp extended community, type=%s, value=%s', comm_code, repr(value_tmp)) + LOG.warning('unknow bgp extended community, type=%s, value=%s', comm_code, repr(value_tmp)) value = value[8:] @@ -174,7 +169,6 @@ def parse(cls, value): @classmethod def construct(cls, value): - """ Construct Extended Community attributes. :param value: value list like [('RT':4837:9929),('RT': 1239:9929)] @@ -257,13 +251,13 @@ def construct(cls, value): elif item[0] == bgp_cons.BGP_EXT_TRA_ACTION: ext_community_hex += struct.pack('!HIBB', item[0], 0, 0, item[1].get('s', 0) * 2 + item[1].get('t', 0)) else: - LOG.warn('unknow bgp extended community for construct, type=%s, value=%s', item[0], item[1]) + LOG.warning('unknow bgp extended community for construct, type=%s, value=%s', item[0], item[1]) if ext_community_hex: return struct.pack('!B', cls.FLAG) + struct.pack( '!B', cls.ID) + struct.pack('!B', len(ext_community_hex)) + ext_community_hex else: - LOG.error('construct error, value=%s' % value) + LOG.error(f'construct error, value={value}') return None diff --git a/yabgp/message/attribute/largecommunity.py b/yabgp/message/attribute/largecommunity.py index 6e7de3eb..cdf85a40 100644 --- a/yabgp/message/attribute/largecommunity.py +++ b/yabgp/message/attribute/largecommunity.py @@ -15,11 +15,9 @@ import struct -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeID -from yabgp.message.attribute import AttributeFlag -from yabgp.common import exception as excep from yabgp.common import constants as bgp_cons +from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class LargeCommunity(Attribute): @@ -45,10 +43,10 @@ def parse(cls, value): large_community = [] if value: try: - length = len(value) / 4 - value_list = list(struct.unpack('!%di' % length, value)) + length = len(value) // 4 + value_list = list(struct.unpack(f'!{length}i', value)) while value_list: - large_community.append("%s:%s:%s" % (value_list[0], value_list[1], value_list[2])) + large_community.append(f"{value_list[0]}:{value_list[1]}:{value_list[2]}") value_list = value_list[3:] except Exception: raise excep.UpdateMessageError( diff --git a/yabgp/message/attribute/linkstate/link/adj_seg_id.py b/yabgp/message/attribute/linkstate/link/adj_seg_id.py index 50e9aa9e..25609138 100644 --- a/yabgp/message/attribute/linkstate/link/adj_seg_id.py +++ b/yabgp/message/attribute/linkstate/link/adj_seg_id.py @@ -16,8 +16,8 @@ import binascii from yabgp.tlv import TLV -from ..linkstate import LinkState +from ..linkstate import LinkState # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 diff --git a/yabgp/message/attribute/linkstate/link/admingroup.py b/yabgp/message/attribute/linkstate/link/admingroup.py index 5d31e6a4..bddf6632 100644 --- a/yabgp/message/attribute/linkstate/link/admingroup.py +++ b/yabgp/message/attribute/linkstate/link/admingroup.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/app_spec_link_attr.py b/yabgp/message/attribute/linkstate/link/app_spec_link_attr.py index da417033..430b6296 100644 --- a/yabgp/message/attribute/linkstate/link/app_spec_link_attr.py +++ b/yabgp/message/attribute/linkstate/link/app_spec_link_attr.py @@ -13,8 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. -import struct import binascii +import struct + from yabgp.message.attribute.linkstate.linkstate import LinkState from yabgp.tlv import TLV @@ -70,10 +71,10 @@ def unpack(cls, data): sabm_bytes = data[offset:offset + sabm_len] if sabm_len == 4: sabm_int = struct.unpack('!I', sabm_bytes)[0] - sabm = '0x{:08x}'.format(sabm_int) + sabm = f'0x{sabm_int:08x}' elif sabm_len == 8: sabm_int = struct.unpack('!Q', sabm_bytes)[0] - sabm = '0x{:016x}'.format(sabm_int) + sabm = f'0x{sabm_int:016x}' else: sabm = '0x' + binascii.b2a_hex(sabm_bytes).decode('ascii') offset += sabm_len @@ -84,10 +85,10 @@ def unpack(cls, data): udabm_bytes = data[offset:offset + udabm_len] if udabm_len == 4: udabm_int = struct.unpack('!I', udabm_bytes)[0] - udabm = '0x{:08x}'.format(udabm_int) + udabm = f'0x{udabm_int:08x}' elif udabm_len == 8: udabm_int = struct.unpack('!Q', udabm_bytes)[0] - udabm = '0x{:016x}'.format(udabm_int) + udabm = f'0x{udabm_int:016x}' else: udabm = '0x' + binascii.b2a_hex(udabm_bytes).decode('ascii') offset += udabm_len diff --git a/yabgp/message/attribute/linkstate/link/extend_admin_group.py b/yabgp/message/attribute/linkstate/link/extend_admin_group.py index 1f14ba73..ccdfeeec 100644 --- a/yabgp/message/attribute/linkstate/link/extend_admin_group.py +++ b/yabgp/message/attribute/linkstate/link/extend_admin_group.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 diff --git a/yabgp/message/attribute/linkstate/link/igp_metric.py b/yabgp/message/attribute/linkstate/link/igp_metric.py index 040e3c9d..21f40bb9 100644 --- a/yabgp/message/attribute/linkstate/link/igp_metric.py +++ b/yabgp/message/attribute/linkstate/link/igp_metric.py @@ -13,10 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. -import struct import binascii +import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/lan_adj_sid.py b/yabgp/message/attribute/linkstate/link/lan_adj_sid.py index 0cb1b8cd..99e48893 100644 --- a/yabgp/message/attribute/linkstate/link/lan_adj_sid.py +++ b/yabgp/message/attribute/linkstate/link/lan_adj_sid.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from __future__ import division import binascii import netaddr diff --git a/yabgp/message/attribute/linkstate/link/link_identifiers.py b/yabgp/message/attribute/linkstate/link/link_identifiers.py index 9d31ac6a..073bf844 100644 --- a/yabgp/message/attribute/linkstate/link/link_identifiers.py +++ b/yabgp/message/attribute/linkstate/link/link_identifiers.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 diff --git a/yabgp/message/attribute/linkstate/link/link_msd.py b/yabgp/message/attribute/linkstate/link/link_msd.py index 4362e309..2bac2d4a 100644 --- a/yabgp/message/attribute/linkstate/link/link_msd.py +++ b/yabgp/message/attribute/linkstate/link/link_msd.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/link_name.py b/yabgp/message/attribute/linkstate/link/link_name.py index 30274407..92d1f61b 100644 --- a/yabgp/message/attribute/linkstate/link/link_name.py +++ b/yabgp/message/attribute/linkstate/link/link_name.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/max_bw.py b/yabgp/message/attribute/linkstate/link/max_bw.py index 6daef46c..54081a03 100644 --- a/yabgp/message/attribute/linkstate/link/max_bw.py +++ b/yabgp/message/attribute/linkstate/link/max_bw.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/max_rsv_bw.py b/yabgp/message/attribute/linkstate/link/max_rsv_bw.py index 6aa2c4c7..80c543f8 100644 --- a/yabgp/message/attribute/linkstate/link/max_rsv_bw.py +++ b/yabgp/message/attribute/linkstate/link/max_rsv_bw.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/min_max_link_delay.py b/yabgp/message/attribute/linkstate/link/min_max_link_delay.py index add308a6..933c8795 100644 --- a/yabgp/message/attribute/linkstate/link/min_max_link_delay.py +++ b/yabgp/message/attribute/linkstate/link/min_max_link_delay.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/opa_link_attr.py b/yabgp/message/attribute/linkstate/link/opa_link_attr.py index 779aca72..846180bf 100644 --- a/yabgp/message/attribute/linkstate/link/opa_link_attr.py +++ b/yabgp/message/attribute/linkstate/link/opa_link_attr.py @@ -14,6 +14,7 @@ # under the License. import struct + from yabgp.message.attribute.linkstate.linkstate import LinkState from yabgp.tlv import TLV @@ -36,4 +37,4 @@ class OpaLinkAttr(TLV): def unpack(cls, value): """ """ - return cls(value=struct.unpack("!%ds" % len(value), value)[0]) + return cls(value=struct.unpack(f"!{len(value)}s", value)[0]) diff --git a/yabgp/message/attribute/linkstate/link/peer_adj_sid.py b/yabgp/message/attribute/linkstate/link/peer_adj_sid.py index ffa820f0..e18261fa 100644 --- a/yabgp/message/attribute/linkstate/link/peer_adj_sid.py +++ b/yabgp/message/attribute/linkstate/link/peer_adj_sid.py @@ -16,8 +16,8 @@ import binascii from yabgp.tlv import TLV -from ..linkstate import LinkState +from ..linkstate import LinkState # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 diff --git a/yabgp/message/attribute/linkstate/link/peer_node_sid.py b/yabgp/message/attribute/linkstate/link/peer_node_sid.py index a1c34860..1e021855 100644 --- a/yabgp/message/attribute/linkstate/link/peer_node_sid.py +++ b/yabgp/message/attribute/linkstate/link/peer_node_sid.py @@ -16,8 +16,8 @@ import binascii from yabgp.tlv import TLV -from ..linkstate import LinkState +from ..linkstate import LinkState # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 diff --git a/yabgp/message/attribute/linkstate/link/peer_set_sid.py b/yabgp/message/attribute/linkstate/link/peer_set_sid.py index 796d5a60..3680a91a 100644 --- a/yabgp/message/attribute/linkstate/link/peer_set_sid.py +++ b/yabgp/message/attribute/linkstate/link/peer_set_sid.py @@ -16,8 +16,8 @@ import binascii from yabgp.tlv import TLV -from ..linkstate import LinkState +from ..linkstate import LinkState # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 diff --git a/yabgp/message/attribute/linkstate/link/remote_router_id.py b/yabgp/message/attribute/linkstate/link/remote_router_id.py index 4cf30340..50b210b5 100644 --- a/yabgp/message/attribute/linkstate/link/remote_router_id.py +++ b/yabgp/message/attribute/linkstate/link/remote_router_id.py @@ -13,8 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. -from yabgp.tlv import TLV from yabgp.net import IPAddress +from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/srlg.py b/yabgp/message/attribute/linkstate/link/srlg.py index bc677024..a56e4af1 100644 --- a/yabgp/message/attribute/linkstate/link/srlg.py +++ b/yabgp/message/attribute/linkstate/link/srlg.py @@ -14,6 +14,7 @@ # under the License. import struct + from yabgp.message.attribute.linkstate.linkstate import LinkState from yabgp.tlv import TLV diff --git a/yabgp/message/attribute/linkstate/link/srv6_end_x_sid.py b/yabgp/message/attribute/linkstate/link/srv6_end_x_sid.py index c21c8664..f16f0e24 100644 --- a/yabgp/message/attribute/linkstate/link/srv6_end_x_sid.py +++ b/yabgp/message/attribute/linkstate/link/srv6_end_x_sid.py @@ -19,6 +19,7 @@ import netaddr from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/srv6_lan_end_x_sid.py b/yabgp/message/attribute/linkstate/link/srv6_lan_end_x_sid.py index 758088c6..100979e3 100644 --- a/yabgp/message/attribute/linkstate/link/srv6_lan_end_x_sid.py +++ b/yabgp/message/attribute/linkstate/link/srv6_lan_end_x_sid.py @@ -13,13 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. +import binascii import struct -import binascii import netaddr from yabgp.net import IPAddress from yabgp.tlv import TLV + from ..linkstate import LinkState @@ -63,7 +64,7 @@ def unpack(cls, data, bgpls_pro_id): n_id_unpack_end_position = 6 + 4 neighbor_id = IPAddress.unpack(data[6:n_id_unpack_end_position]) else: - raise Exception('Unknown bgpls_pro_id {0}'.format(bgpls_pro_id)) + raise Exception(f'Unknown bgpls_pro_id {bgpls_pro_id}') sid = str( netaddr.IPAddress(int(binascii.b2a_hex(data[n_id_unpack_end_position:n_id_unpack_end_position + 16]), 16)) diff --git a/yabgp/message/attribute/linkstate/link/srv6_sid.py b/yabgp/message/attribute/linkstate/link/srv6_sid.py index dd00b569..048fda6c 100644 --- a/yabgp/message/attribute/linkstate/link/srv6_sid.py +++ b/yabgp/message/attribute/linkstate/link/srv6_sid.py @@ -14,6 +14,7 @@ # under the License. from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/te_metric.py b/yabgp/message/attribute/linkstate/link/te_metric.py index fd0a7e14..266d9e88 100644 --- a/yabgp/message/attribute/linkstate/link/te_metric.py +++ b/yabgp/message/attribute/linkstate/link/te_metric.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/unidirect_avail_bw.py b/yabgp/message/attribute/linkstate/link/unidirect_avail_bw.py index 06ac94db..70d6df88 100644 --- a/yabgp/message/attribute/linkstate/link/unidirect_avail_bw.py +++ b/yabgp/message/attribute/linkstate/link/unidirect_avail_bw.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/unidirect_bw_util.py b/yabgp/message/attribute/linkstate/link/unidirect_bw_util.py index d0d92714..8ce688a7 100644 --- a/yabgp/message/attribute/linkstate/link/unidirect_bw_util.py +++ b/yabgp/message/attribute/linkstate/link/unidirect_bw_util.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/unidirect_delay_var.py b/yabgp/message/attribute/linkstate/link/unidirect_delay_var.py index d47d6208..c4f6dcba 100644 --- a/yabgp/message/attribute/linkstate/link/unidirect_delay_var.py +++ b/yabgp/message/attribute/linkstate/link/unidirect_delay_var.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/unidirect_link_delay.py b/yabgp/message/attribute/linkstate/link/unidirect_link_delay.py index f6a83baa..a68189db 100644 --- a/yabgp/message/attribute/linkstate/link/unidirect_link_delay.py +++ b/yabgp/message/attribute/linkstate/link/unidirect_link_delay.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/unidirect_packet_loss.py b/yabgp/message/attribute/linkstate/link/unidirect_packet_loss.py index 2a3b563b..8cf0fe20 100644 --- a/yabgp/message/attribute/linkstate/link/unidirect_packet_loss.py +++ b/yabgp/message/attribute/linkstate/link/unidirect_packet_loss.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/unidirect_residual_bw.py b/yabgp/message/attribute/linkstate/link/unidirect_residual_bw.py index e5530789..22a2d5da 100644 --- a/yabgp/message/attribute/linkstate/link/unidirect_residual_bw.py +++ b/yabgp/message/attribute/linkstate/link/unidirect_residual_bw.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/link/unsrv_bw.py b/yabgp/message/attribute/linkstate/link/unsrv_bw.py index c47df415..6a13b4c7 100644 --- a/yabgp/message/attribute/linkstate/link/unsrv_bw.py +++ b/yabgp/message/attribute/linkstate/link/unsrv_bw.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/linkstate.py b/yabgp/message/attribute/linkstate/linkstate.py index fb0cccd4..ffe4468d 100644 --- a/yabgp/message/attribute/linkstate/linkstate.py +++ b/yabgp/message/attribute/linkstate/linkstate.py @@ -12,11 +12,11 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import binascii import logging import struct - -import binascii import traceback + from yabgp.common import constants as bgp_cons from yabgp.common import exception as excep from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID diff --git a/yabgp/message/attribute/linkstate/node/flex_algo_define.py b/yabgp/message/attribute/linkstate/node/flex_algo_define.py index 17d24ea1..f86ce8d1 100644 --- a/yabgp/message/attribute/linkstate/node/flex_algo_define.py +++ b/yabgp/message/attribute/linkstate/node/flex_algo_define.py @@ -13,8 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. -import struct import binascii +import struct from yabgp.message.attribute.linkstate.linkstate import LinkState from yabgp.tlv import TLV diff --git a/yabgp/message/attribute/linkstate/node/isisarea.py b/yabgp/message/attribute/linkstate/node/isisarea.py index 91a1988a..cdfc710d 100644 --- a/yabgp/message/attribute/linkstate/node/isisarea.py +++ b/yabgp/message/attribute/linkstate/node/isisarea.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/node/local_router_id.py b/yabgp/message/attribute/linkstate/node/local_router_id.py index 433ad583..b66f70d0 100644 --- a/yabgp/message/attribute/linkstate/node/local_router_id.py +++ b/yabgp/message/attribute/linkstate/node/local_router_id.py @@ -15,6 +15,7 @@ from yabgp.net import IPAddress from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/node/mt_id.py b/yabgp/message/attribute/linkstate/node/mt_id.py index 10198c65..8c4e709c 100644 --- a/yabgp/message/attribute/linkstate/node/mt_id.py +++ b/yabgp/message/attribute/linkstate/node/mt_id.py @@ -14,7 +14,9 @@ # under the License. import struct + from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/node/name.py b/yabgp/message/attribute/linkstate/node/name.py index c2ca0be6..80df7962 100644 --- a/yabgp/message/attribute/linkstate/node/name.py +++ b/yabgp/message/attribute/linkstate/node/name.py @@ -14,6 +14,7 @@ # under the License. from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/node/node_msd.py b/yabgp/message/attribute/linkstate/node/node_msd.py index 502e98b9..68b12de6 100644 --- a/yabgp/message/attribute/linkstate/node/node_msd.py +++ b/yabgp/message/attribute/linkstate/node/node_msd.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/node/nodeflags.py b/yabgp/message/attribute/linkstate/node/nodeflags.py index f6761fb7..4035b4f4 100644 --- a/yabgp/message/attribute/linkstate/node/nodeflags.py +++ b/yabgp/message/attribute/linkstate/node/nodeflags.py @@ -48,10 +48,10 @@ def unpack(cls, value): """ """ valueByte = ord(value[0:1]) - O = valueByte >> 7 + overload = valueByte >> 7 T = (valueByte << 1) % 256 >> 7 E = (valueByte << 2) % 256 >> 7 B = (valueByte << 3) % 256 >> 7 R = (valueByte << 4) % 256 >> 7 V = (valueByte << 5) % 256 >> 7 - return cls(value={"O": O, "T": T, "E": E, "B": B, "R": R, "V": V}) + return cls(value={"O": overload, "T": T, "E": E, "B": B, "R": R, "V": V}) diff --git a/yabgp/message/attribute/linkstate/node/opa_node_attr.py b/yabgp/message/attribute/linkstate/node/opa_node_attr.py index 2cbb320d..2de49421 100644 --- a/yabgp/message/attribute/linkstate/node/opa_node_attr.py +++ b/yabgp/message/attribute/linkstate/node/opa_node_attr.py @@ -14,6 +14,7 @@ # under the License. import struct + from yabgp.message.attribute.linkstate.linkstate import LinkState from yabgp.tlv import TLV @@ -36,4 +37,4 @@ class OpaNodeAttr(TLV): def unpack(cls, value): """ """ - return cls(value=struct.unpack("!%ds" % len(value), value)[0]) + return cls(value=struct.unpack(f"!{len(value)}s", value)[0]) diff --git a/yabgp/message/attribute/linkstate/node/sid_or_label.py b/yabgp/message/attribute/linkstate/node/sid_or_label.py index 65b0702a..b082366c 100644 --- a/yabgp/message/attribute/linkstate/node/sid_or_label.py +++ b/yabgp/message/attribute/linkstate/node/sid_or_label.py @@ -14,6 +14,7 @@ # under the License. import struct + from yabgp.message.attribute.linkstate.linkstate import LinkState from yabgp.tlv import TLV diff --git a/yabgp/message/attribute/linkstate/node/sr_algorithm.py b/yabgp/message/attribute/linkstate/node/sr_algorithm.py index f2fc9762..be52f46b 100644 --- a/yabgp/message/attribute/linkstate/node/sr_algorithm.py +++ b/yabgp/message/attribute/linkstate/node/sr_algorithm.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 diff --git a/yabgp/message/attribute/linkstate/node/sr_capabilities.py b/yabgp/message/attribute/linkstate/node/sr_capabilities.py index 4038b3ca..9d8055d8 100644 --- a/yabgp/message/attribute/linkstate/node/sr_capabilities.py +++ b/yabgp/message/attribute/linkstate/node/sr_capabilities.py @@ -44,7 +44,7 @@ def unpack(cls, value): """ """ flags = ord(value[0:1]) - I = flags >> 7 + index_flag = flags >> 7 V = (flags << 1) % 256 >> 7 value = value[2:] results = [] @@ -65,4 +65,4 @@ def unpack(cls, value): value = value[7 + length:] tmp['sid'] = data results.append(tmp) - return cls(value={"flag": {"I": I, "V": V}, "value": results}) + return cls(value={"flag": {"I": index_flag, "V": V}, "value": results}) diff --git a/yabgp/message/attribute/linkstate/node/srv6_capabilities.py b/yabgp/message/attribute/linkstate/node/srv6_capabilities.py index 53a294d9..fdcaec3f 100644 --- a/yabgp/message/attribute/linkstate/node/srv6_capabilities.py +++ b/yabgp/message/attribute/linkstate/node/srv6_capabilities.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/prefix/ext_igp_route_tag_list.py b/yabgp/message/attribute/linkstate/prefix/ext_igp_route_tag_list.py index 2e4cae26..fb23ba35 100644 --- a/yabgp/message/attribute/linkstate/prefix/ext_igp_route_tag_list.py +++ b/yabgp/message/attribute/linkstate/prefix/ext_igp_route_tag_list.py @@ -14,6 +14,7 @@ # under the License. import struct + from yabgp.message.attribute.linkstate.linkstate import LinkState from yabgp.tlv import TLV diff --git a/yabgp/message/attribute/linkstate/prefix/igp_route_tag_list.py b/yabgp/message/attribute/linkstate/prefix/igp_route_tag_list.py index 1b67c0ef..0f2883c6 100644 --- a/yabgp/message/attribute/linkstate/prefix/igp_route_tag_list.py +++ b/yabgp/message/attribute/linkstate/prefix/igp_route_tag_list.py @@ -14,6 +14,7 @@ # under the License. import struct + from yabgp.message.attribute.linkstate.linkstate import LinkState from yabgp.tlv import TLV diff --git a/yabgp/message/attribute/linkstate/prefix/ospf_forward_addr.py b/yabgp/message/attribute/linkstate/prefix/ospf_forward_addr.py index 24ed5562..debce486 100644 --- a/yabgp/message/attribute/linkstate/prefix/ospf_forward_addr.py +++ b/yabgp/message/attribute/linkstate/prefix/ospf_forward_addr.py @@ -15,11 +15,11 @@ import binascii -from yabgp.message.attribute.linkstate.linkstate import LinkState -from yabgp.common.tlv import TLV - import netaddr +from yabgp.common.tlv import TLV +from yabgp.message.attribute.linkstate.linkstate import LinkState + @LinkState.register() class OspfForwardingAddr(TLV): diff --git a/yabgp/message/attribute/linkstate/prefix/prefix_igp_attr.py b/yabgp/message/attribute/linkstate/prefix/prefix_igp_attr.py index 6f15f2be..088c5f6f 100644 --- a/yabgp/message/attribute/linkstate/prefix/prefix_igp_attr.py +++ b/yabgp/message/attribute/linkstate/prefix/prefix_igp_attr.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 diff --git a/yabgp/message/attribute/linkstate/prefix/prefix_metric.py b/yabgp/message/attribute/linkstate/prefix/prefix_metric.py index cf02d645..fe826e79 100644 --- a/yabgp/message/attribute/linkstate/prefix/prefix_metric.py +++ b/yabgp/message/attribute/linkstate/prefix/prefix_metric.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/prefix/prefix_sid.py b/yabgp/message/attribute/linkstate/prefix/prefix_sid.py index 9cdab4bf..b25333c7 100644 --- a/yabgp/message/attribute/linkstate/prefix/prefix_sid.py +++ b/yabgp/message/attribute/linkstate/prefix/prefix_sid.py @@ -16,6 +16,7 @@ import binascii from yabgp.tlv import TLV + from ..linkstate import LinkState # https://datatracker.ietf.org/doc/draft-ietf-idr-bgp-ls-segment-routing-ext/?include_text=1 diff --git a/yabgp/message/attribute/linkstate/prefix/src_router_id.py b/yabgp/message/attribute/linkstate/prefix/src_router_id.py index eceb53d7..758f81e0 100644 --- a/yabgp/message/attribute/linkstate/prefix/src_router_id.py +++ b/yabgp/message/attribute/linkstate/prefix/src_router_id.py @@ -13,14 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. +import binascii import struct -import binascii import netaddr from yabgp.tlv import TLV -from ..linkstate import LinkState +from ..linkstate import LinkState # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ diff --git a/yabgp/message/attribute/linkstate/prefix/srv6_locator.py b/yabgp/message/attribute/linkstate/prefix/srv6_locator.py index 2fd7d165..341df6f1 100644 --- a/yabgp/message/attribute/linkstate/prefix/srv6_locator.py +++ b/yabgp/message/attribute/linkstate/prefix/srv6_locator.py @@ -13,11 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. -import struct - import binascii +import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/srv6_sid/srv6_bgp_peer_node_sid.py b/yabgp/message/attribute/linkstate/srv6_sid/srv6_bgp_peer_node_sid.py index 64d3f86e..eed7ebaa 100644 --- a/yabgp/message/attribute/linkstate/srv6_sid/srv6_bgp_peer_node_sid.py +++ b/yabgp/message/attribute/linkstate/srv6_sid/srv6_bgp_peer_node_sid.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/linkstate/srv6_sid/srv6_endpoint_behavior.py b/yabgp/message/attribute/linkstate/srv6_sid/srv6_endpoint_behavior.py index b1ab8b35..6561f74a 100644 --- a/yabgp/message/attribute/linkstate/srv6_sid/srv6_endpoint_behavior.py +++ b/yabgp/message/attribute/linkstate/srv6_sid/srv6_endpoint_behavior.py @@ -16,6 +16,7 @@ import struct from yabgp.tlv import TLV + from ..linkstate import LinkState diff --git a/yabgp/message/attribute/localpref.py b/yabgp/message/attribute/localpref.py index 115c673a..5c46029f 100644 --- a/yabgp/message/attribute/localpref.py +++ b/yabgp/message/attribute/localpref.py @@ -15,11 +15,9 @@ import struct -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeID -from yabgp.message.attribute import AttributeFlag from yabgp.common import constants as bgp_cons from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class LocalPreference(Attribute): @@ -35,14 +33,13 @@ class LocalPreference(Attribute): @classmethod def parse(cls, value): - """ parse bgp local preference attribute :param value: raw binary value """ try: return struct.unpack('!I', value)[0] - except: + except struct.error: raise excep.UpdateMessageError( sub_error=bgp_cons.ERR_MSG_UPDATE_ATTR_LEN, data=value) diff --git a/yabgp/message/attribute/med.py b/yabgp/message/attribute/med.py index c3d36c77..1156f93e 100644 --- a/yabgp/message/attribute/med.py +++ b/yabgp/message/attribute/med.py @@ -15,11 +15,9 @@ import struct -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeID -from yabgp.message.attribute import AttributeFlag from yabgp.common import constants as bgp_cons from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class MED(Attribute): @@ -43,7 +41,7 @@ def parse(cls, value): """ try: return struct.unpack('!I', value)[0] - except: + except struct.error: raise excep.UpdateMessageError( sub_error=bgp_cons.ERR_MSG_UPDATE_ATTR_LEN, data=value) diff --git a/yabgp/message/attribute/mpreachnlri.py b/yabgp/message/attribute/mpreachnlri.py index 562ca5b4..35faaf3f 100644 --- a/yabgp/message/attribute/mpreachnlri.py +++ b/yabgp/message/attribute/mpreachnlri.py @@ -16,28 +16,25 @@ """BGP Attribute MP_REACH_NLRI """ -import struct import binascii +import struct import netaddr -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeFlag -from yabgp.message.attribute import AttributeID -from yabgp.common import afn -from yabgp.common import safn -from yabgp.common import exception as excep +from yabgp.common import afn, safn from yabgp.common import constants as bgp_cons -from yabgp.message.attribute.nlri.ipv4_mpls_vpn import IPv4MPLSVPN -from yabgp.message.attribute.nlri.ipv6_mpls_vpn import IPv6MPLSVPN +from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID +from yabgp.message.attribute.nlri.evpn import EVPN from yabgp.message.attribute.nlri.ipv4_flowspec import IPv4FlowSpec -from yabgp.message.attribute.nlri.ipv6_flowspec import IPv6FlowSpec +from yabgp.message.attribute.nlri.ipv4_mpls_vpn import IPv4MPLSVPN from yabgp.message.attribute.nlri.ipv4_srte import IPv4SRTE +from yabgp.message.attribute.nlri.ipv4_unicast import IPv4Unicast +from yabgp.message.attribute.nlri.ipv6_flowspec import IPv6FlowSpec +from yabgp.message.attribute.nlri.ipv6_mpls_vpn import IPv6MPLSVPN from yabgp.message.attribute.nlri.ipv6_unicast import IPv6Unicast from yabgp.message.attribute.nlri.labeled_unicast.ipv4 import IPv4LabeledUnicast from yabgp.message.attribute.nlri.labeled_unicast.ipv6 import IPv6LabeledUnicast -from yabgp.message.attribute.nlri.ipv4_unicast import IPv4Unicast -from yabgp.message.attribute.nlri.evpn import EVPN from yabgp.message.attribute.nlri.linkstate import BGPLS @@ -249,7 +246,7 @@ def construct(cls, value): + struct.pack('!H', len(attr_value)) + attr_value except Exception as e: raise excep.ConstructAttributeFailed( - reason='failed to construct attributes: %s' % e, + reason=f'failed to construct attributes: {e}', data=value ) elif safi == safn.SAFNUM_SRTE: # BGP SR TE Policy @@ -267,7 +264,7 @@ def construct(cls, value): + struct.pack('!H', len(attr_value)) + attr_value except Exception as e: raise excep.ConstructAttributeFailed( - reason='failed to construct attributes: %s' % e, + reason=f'failed to construct attributes: {e}', data=value ) elif safi == safn.SAFNUM_MPLS_LABEL: @@ -285,7 +282,7 @@ def construct(cls, value): + struct.pack('!H', len(attr_value)) + attr_value except Exception as e: raise excep.ConstructAttributeFailed( - reason='failed to construct attributes: %s' % e, + reason=f'failed to construct attributes: {e}', data=value ) else: @@ -318,7 +315,7 @@ def construct(cls, value): + struct.pack('!H', len(attr_value)) + attr_value except Exception as e: raise excep.ConstructAttributeFailed( - reason='failed to construct attributes: %s' % e, + reason=f'failed to construct attributes: {e}', data=value ) elif safi == safn.SAFNUM_UNICAST: @@ -349,7 +346,7 @@ def construct(cls, value): + struct.pack('!H', len(attr_value)) + attr_value except Exception as e: raise excep.ConstructAttributeFailed( - reason='failed to construct attributes: %s' % e, + reason=f'failed to construct attributes: {e}', data=value ) else: diff --git a/yabgp/message/attribute/mpunreachnlri.py b/yabgp/message/attribute/mpunreachnlri.py index 056e10bb..1a0dc184 100644 --- a/yabgp/message/attribute/mpunreachnlri.py +++ b/yabgp/message/attribute/mpunreachnlri.py @@ -18,22 +18,19 @@ import struct -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeFlag -from yabgp.message.attribute import AttributeID +from yabgp.common import afn, safn +from yabgp.common import constants as bgp_cons +from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID +from yabgp.message.attribute.nlri.evpn import EVPN +from yabgp.message.attribute.nlri.ipv4_flowspec import IPv4FlowSpec from yabgp.message.attribute.nlri.ipv4_mpls_vpn import IPv4MPLSVPN +from yabgp.message.attribute.nlri.ipv4_srte import IPv4SRTE from yabgp.message.attribute.nlri.ipv4_unicast import IPv4Unicast from yabgp.message.attribute.nlri.ipv6_mpls_vpn import IPv6MPLSVPN -from yabgp.message.attribute.nlri.ipv4_flowspec import IPv4FlowSpec from yabgp.message.attribute.nlri.ipv6_unicast import IPv6Unicast from yabgp.message.attribute.nlri.labeled_unicast.ipv4 import IPv4LabeledUnicast -from yabgp.message.attribute.nlri.evpn import EVPN from yabgp.message.attribute.nlri.linkstate import BGPLS -from yabgp.message.attribute.nlri.ipv4_srte import IPv4SRTE -from yabgp.common import afn -from yabgp.common import safn -from yabgp.common import exception as excep -from yabgp.common import constants as bgp_cons class MpUnReachNLRI(Attribute): diff --git a/yabgp/message/attribute/nexthop.py b/yabgp/message/attribute/nexthop.py index 22d32e45..0703ec50 100644 --- a/yabgp/message/attribute/nexthop.py +++ b/yabgp/message/attribute/nexthop.py @@ -13,16 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. -import struct import binascii +import struct import netaddr -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeID -from yabgp.message.attribute import AttributeFlag from yabgp.common import constants as bgp_cons from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class NextHop(Attribute): diff --git a/yabgp/message/attribute/nlri/__init__.py b/yabgp/message/attribute/nlri/__init__.py index 9d172e85..b6cd0b76 100644 --- a/yabgp/message/attribute/nlri/__init__.py +++ b/yabgp/message/attribute/nlri/__init__.py @@ -13,14 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. -from __future__ import division -import struct import binascii +import struct import netaddr -class NLRI(object): +class NLRI: @classmethod def parse(cls, *args): diff --git a/yabgp/message/attribute/nlri/evpn.py b/yabgp/message/attribute/nlri/evpn.py index 5d23753b..0e3ae966 100644 --- a/yabgp/message/attribute/nlri/evpn.py +++ b/yabgp/message/attribute/nlri/evpn.py @@ -13,15 +13,15 @@ # License for the specific language governing permissions and limitations # under the License. -from __future__ import division -import struct import binascii +import struct import netaddr -from yabgp.common import afn -from yabgp.common import safn + +from yabgp.common import afn, safn from yabgp.common import constants as bgp_cons from yabgp.message.attribute.nlri import NLRI + # from yabgp.message.attribute.nlri.mpls_vpn import MPLSVPN @@ -93,7 +93,7 @@ def signal_evpn_overlay(attr_dict): try: afi_safi = tuple(attr_dict.get(bgp_cons.BGPTYPE_MP_REACH_NLRI).get('afi_safi')) community_ext = attr_dict.get(bgp_cons.BGPTYPE_EXTENDED_COMMUNITY) - except: + except Exception: return evpn_overlay if afi_safi == (afn.AFNUM_L2VPN, safn.SAFNUM_EVPN): evpn_overlay['evpn'] = True @@ -116,14 +116,14 @@ def parse_rd(cls, data): rd_value = data[2:8] if rd_type == bgp_cons.BGP_ROUTE_DISTINGUISHER_TYPE_0: asn, an = struct.unpack('!HI', rd_value) - rd = '%s:%s' % (asn, an) + rd = f'{asn}:{an}' elif rd_type == bgp_cons.BGP_ROUTE_DISTINGUISHER_TYPE_1: ip = str(netaddr.IPAddress(struct.unpack('!I', rd_value[0:4])[0])) an = struct.unpack('!H', rd_value[4:6])[0] - rd = '%s:%s' % (ip, an) + rd = f'{ip}:{an}' elif rd_type == bgp_cons.BGP_ROUTE_DISTINGUISHER_TYPE_2: asn, an = struct.unpack('!IH', rd_value) - rd = '%s:%s' % (asn, an) + rd = f'{asn}:{an}' else: # fixme(by xiaopeng163) for other rd type process rd = str(rd_value) @@ -492,7 +492,7 @@ def parse(cls, value, iswithdraw=False): # ipv6 offset = 16 - route['prefix'] = '%s/%s' % (str(netaddr.IPAddress(int(binascii.b2a_hex(value[0: offset]), 16))), ip_addr_len) + route['prefix'] = f'{str(netaddr.IPAddress(int(binascii.b2a_hex(value[0: offset]), 16)))}/{ip_addr_len}' value = value[offset:] route['gateway'] = str(netaddr.IPAddress(int(binascii.b2a_hex(value[0: offset]), 16))) value = value[offset:] diff --git a/yabgp/message/attribute/nlri/ipv4_flowspec.py b/yabgp/message/attribute/nlri/ipv4_flowspec.py index 23858d41..a08c1ec5 100644 --- a/yabgp/message/attribute/nlri/ipv4_flowspec.py +++ b/yabgp/message/attribute/nlri/ipv4_flowspec.py @@ -15,8 +15,6 @@ """IPv4 Flowspec NLRI """ -from __future__ import division -from builtins import range import binascii import math import struct @@ -78,7 +76,7 @@ def construct(cls, value): def construct_nlri(cls, data): """ Construct NLRI """ # there may have many filters in each nlri - data = dict([(int(l), r) for (l, r) in data.items()]) + data = dict([(int(k), r) for (k, r) in data.items()]) nlri_tmp = b'' for type_tmp in [bgp_cons.BGPNLRI_FSPEC_DST_PFIX, bgp_cons.BGPNLRI_FSPEC_SRC_PFIX]: if data.get(type_tmp): @@ -114,7 +112,7 @@ def parse_prefix(data): else: prefix_data = [ord(i) for i in tmp] prefix_data = prefix_data + list(str(0)) * 4 - prefix = "%s.%s.%s.%s" % (tuple(prefix_data[0:4])) + '/' + str(prefix_len) + prefix = "{}.{}.{}.{}".format(*tuple(prefix_data[0:4])) + '/' + str(prefix_len) return prefix, octet_len + 1 @classmethod diff --git a/yabgp/message/attribute/nlri/ipv4_mpls_vpn.py b/yabgp/message/attribute/nlri/ipv4_mpls_vpn.py index b4e94a76..1883a463 100644 --- a/yabgp/message/attribute/nlri/ipv4_mpls_vpn.py +++ b/yabgp/message/attribute/nlri/ipv4_mpls_vpn.py @@ -1,4 +1,3 @@ -# coding=utf-8 # Copyright 2015 Cisco Systems, Inc. # All rights reserved. # @@ -14,9 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. -from yabgp.message.attribute.nlri.mpls_vpn import MPLSVPN from yabgp.common.afn import AFNUM_INET from yabgp.common.safn import SAFNUM_LAB_VPNUNICAST +from yabgp.message.attribute.nlri.mpls_vpn import MPLSVPN class IPv4MPLSVPN(MPLSVPN): @@ -29,8 +28,8 @@ class IPv4MPLSVPN(MPLSVPN): @classmethod def parse(cls, value, iswithdraw=False, addpath=False): - return super(IPv4MPLSVPN, cls).parse(value, iswithdraw=iswithdraw, addpath=addpath) + return super().parse(value, iswithdraw=iswithdraw, addpath=addpath) @classmethod def construct(cls, value, iswithdraw=False): - return super(IPv4MPLSVPN, cls).construct(value, iswithdraw=iswithdraw) + return super().construct(value, iswithdraw=iswithdraw) diff --git a/yabgp/message/attribute/nlri/ipv4_srte.py b/yabgp/message/attribute/nlri/ipv4_srte.py index 84763af4..0e08debb 100644 --- a/yabgp/message/attribute/nlri/ipv4_srte.py +++ b/yabgp/message/attribute/nlri/ipv4_srte.py @@ -16,6 +16,7 @@ """IPv4 SR TE Policy NLRI """ import struct + import netaddr from yabgp.message.attribute.nlri import NLRI diff --git a/yabgp/message/attribute/nlri/ipv4_unicast.py b/yabgp/message/attribute/nlri/ipv4_unicast.py index 5227abc6..7334f3ee 100644 --- a/yabgp/message/attribute/nlri/ipv4_unicast.py +++ b/yabgp/message/attribute/nlri/ipv4_unicast.py @@ -1,5 +1,4 @@ # !/usr/bin/env python -# -*- coding:utf-8 -*- """ dalian-stc-dev@cisco.com Copyright 2019 Cisco Systems, Inc. @@ -20,15 +19,15 @@ """ IPv4 Unicast """ -import struct import binascii import logging +import struct + import netaddr -from yabgp.message.attribute.nlri import NLRI -from yabgp.common import exception as excep from yabgp.common import constants as bgp_cons - +from yabgp.common import exception as excep +from yabgp.message.attribute.nlri import NLRI LOG = logging.getLogger() @@ -76,7 +75,7 @@ def parse(nlri_data, addpath=False): if remainder > 0: prefix_data[-1] &= 255 << (8 - remainder) prefix_data = prefix_data + list(str(0)) * 4 - prefix = "%s.%s.%s.%s" % (tuple(prefix_data[0:4])) + '/' + str(prefix_len) + prefix = "{}.{}.{}.{}".format(*tuple(prefix_data[0:4])) + '/' + str(prefix_len) if not addpath: prefixes.append(prefix) else: diff --git a/yabgp/message/attribute/nlri/ipv6_flowspec.py b/yabgp/message/attribute/nlri/ipv6_flowspec.py index 3492c9d8..2a547e5b 100644 --- a/yabgp/message/attribute/nlri/ipv6_flowspec.py +++ b/yabgp/message/attribute/nlri/ipv6_flowspec.py @@ -15,8 +15,6 @@ """IPv6 Flowspec NLRI """ -from __future__ import division -from builtins import range import binascii import math import struct @@ -78,7 +76,7 @@ def construct(cls, value): def construct_nlri(cls, data): """ Construct NLRI """ # there may have many filters in each nlri - data = dict([(int(l), r) for (l, r) in data.items()]) + data = dict([(int(k), r) for (k, r) in data.items()]) nlri_tmp = b'' for type_tmp in [bgp_cons.BGPNLRI_IPV6_FSPEC_DST_PFIX, bgp_cons.BGPNLRI_IPV6_FSPEC_SRC_PFIX]: if data.get(type_tmp): @@ -116,7 +114,7 @@ def parse_prefix(data): else: prefix_data = [ord(i) for i in tmp] prefix_data = prefix_data + list(str(0)) * 4 - prefix = "%s.%s.%s.%s" % (tuple(prefix_data[0:4])) + '/' + str(prefix_len) + prefix = "{}.{}.{}.{}".format(*tuple(prefix_data[0:4])) + '/' + str(prefix_len) return prefix, octet_len + 1 @classmethod diff --git a/yabgp/message/attribute/nlri/ipv6_mpls_vpn.py b/yabgp/message/attribute/nlri/ipv6_mpls_vpn.py index 39962cf1..6c80ed4f 100644 --- a/yabgp/message/attribute/nlri/ipv6_mpls_vpn.py +++ b/yabgp/message/attribute/nlri/ipv6_mpls_vpn.py @@ -13,9 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. -from yabgp.message.attribute.nlri.mpls_vpn import MPLSVPN from yabgp.common.afn import AFNUM_INET6 from yabgp.common.safn import SAFNUM_LAB_VPNUNICAST +from yabgp.message.attribute.nlri.mpls_vpn import MPLSVPN class IPv6MPLSVPN(MPLSVPN): @@ -27,8 +27,8 @@ class IPv6MPLSVPN(MPLSVPN): @classmethod def parse(cls, value, iswithdraw=False, addpath=False): - return super(IPv6MPLSVPN, cls).parse(value, iswithdraw=iswithdraw, addpath=addpath) + return super().parse(value, iswithdraw=iswithdraw, addpath=addpath) @classmethod def construct(cls, value, iswithdraw=False): - return super(IPv6MPLSVPN, cls).construct(value, iswithdraw=iswithdraw) + return super().construct(value, iswithdraw=iswithdraw) diff --git a/yabgp/message/attribute/nlri/ipv6_unicast.py b/yabgp/message/attribute/nlri/ipv6_unicast.py index aba258ff..f1ef9577 100644 --- a/yabgp/message/attribute/nlri/ipv6_unicast.py +++ b/yabgp/message/attribute/nlri/ipv6_unicast.py @@ -15,8 +15,9 @@ """ IPv6 Unicast """ -import struct import binascii +import struct + import netaddr from yabgp.message.attribute.nlri import NLRI @@ -56,7 +57,7 @@ def parse(cls, nlri_data, addpath=False): for i in range(0, zero_len): prefix_bit += b'\x00' - prefix_addr = str(netaddr.IPAddress(int(binascii.b2a_hex(prefix_bit), 16))) + '/%s' % prefix_bit_len + prefix_addr = str(netaddr.IPAddress(int(binascii.b2a_hex(prefix_bit), 16))) + f'/{prefix_bit_len}' if addpath: nlri_list.append({'prefix': prefix_addr, 'path_id': path_id}) else: diff --git a/yabgp/message/attribute/nlri/labeled_unicast/__init__.py b/yabgp/message/attribute/nlri/labeled_unicast/__init__.py index bea12a3d..ed480d91 100644 --- a/yabgp/message/attribute/nlri/labeled_unicast/__init__.py +++ b/yabgp/message/attribute/nlri/labeled_unicast/__init__.py @@ -13,16 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. -from __future__ import division -import struct import binascii - +import struct import netaddr -from yabgp.message.attribute.nlri import NLRI -from yabgp.common.afn import AFNUM_INET6, AFNUM_INET +from yabgp.common.afn import AFNUM_INET, AFNUM_INET6 from yabgp.common.safn import SAFNUM_MPLS_LABEL +from yabgp.message.attribute.nlri import NLRI class LabeledUnicast(NLRI): diff --git a/yabgp/message/attribute/nlri/labeled_unicast/ipv4.py b/yabgp/message/attribute/nlri/labeled_unicast/ipv4.py index f74d3dcc..76f0f4d7 100644 --- a/yabgp/message/attribute/nlri/labeled_unicast/ipv4.py +++ b/yabgp/message/attribute/nlri/labeled_unicast/ipv4.py @@ -13,8 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. -from yabgp.message.attribute.nlri.labeled_unicast import LabeledUnicast from yabgp.common.afn import AFNUM_INET +from yabgp.message.attribute.nlri.labeled_unicast import LabeledUnicast class IPv4LabeledUnicast(LabeledUnicast): diff --git a/yabgp/message/attribute/nlri/labeled_unicast/ipv6.py b/yabgp/message/attribute/nlri/labeled_unicast/ipv6.py index 8530905b..bcffaa60 100644 --- a/yabgp/message/attribute/nlri/labeled_unicast/ipv6.py +++ b/yabgp/message/attribute/nlri/labeled_unicast/ipv6.py @@ -13,8 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. -from yabgp.message.attribute.nlri.labeled_unicast import LabeledUnicast from yabgp.common.afn import AFNUM_INET6 +from yabgp.message.attribute.nlri.labeled_unicast import LabeledUnicast class IPv6LabeledUnicast(LabeledUnicast): diff --git a/yabgp/message/attribute/nlri/linkstate.py b/yabgp/message/attribute/nlri/linkstate.py index c394c701..f85d2dc3 100644 --- a/yabgp/message/attribute/nlri/linkstate.py +++ b/yabgp/message/attribute/nlri/linkstate.py @@ -15,9 +15,8 @@ """linkstate """ -from __future__ import division -import struct import binascii +import struct import netaddr @@ -161,7 +160,7 @@ def parse_nlri(cls, data, nlri_type): for i in range(0, (128 - mask) // 8): prefix_bit += b'\x00' ip_str = str(netaddr.IPAddress(int(binascii.b2a_hex(prefix_bit), 16))) - descriptor['value'] = "%s/%s" % (ip_str, mask) + descriptor['value'] = f"{ip_str}/{mask}" elif _type == 518: # SRv6 SID Information # Refer: https://datatracker.ietf.org/doc/html/draft-ietf-idr-bgpls-srv6-ext-14#section-6.1 descriptor['type'] = 'srv6_sid_information' diff --git a/yabgp/message/attribute/nlri/mpls_vpn.py b/yabgp/message/attribute/nlri/mpls_vpn.py index b0f4233b..0b127e67 100644 --- a/yabgp/message/attribute/nlri/mpls_vpn.py +++ b/yabgp/message/attribute/nlri/mpls_vpn.py @@ -13,13 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. -import struct import binascii +import struct import netaddr -from yabgp.common import afn -from yabgp.common import safn +from yabgp.common import afn, safn from yabgp.common import constants as bgp_cons from yabgp.message.attribute.nlri import NLRI @@ -159,14 +158,14 @@ def parse_rd(cls, data): rd_value = data[2:8] if rd_type == bgp_cons.BGP_ROUTE_DISTINGUISHER_TYPE_0: asn, an = struct.unpack('!HI', rd_value) - rd = '%s:%s' % (asn, an) + rd = f'{asn}:{an}' elif rd_type == bgp_cons.BGP_ROUTE_DISTINGUISHER_TYPE_1: ip = str(netaddr.IPAddress(struct.unpack('!I', rd_value[0:4])[0])) an = struct.unpack('!H', rd_value[4:6])[0] - rd = '%s:%s' % (ip, an) + rd = f'{ip}:{an}' elif rd_type == bgp_cons.BGP_ROUTE_DISTINGUISHER_TYPE_2: asn, an = struct.unpack('!IH', rd_value) - rd = '%s:%s' % (asn, an) + rd = f'{asn}:{an}' else: # fixme(by xiaopeng163) for other rd type process rd = str(rd_value) diff --git a/yabgp/message/attribute/origin.py b/yabgp/message/attribute/origin.py index 9f71e0c6..70a4971e 100644 --- a/yabgp/message/attribute/origin.py +++ b/yabgp/message/attribute/origin.py @@ -15,11 +15,9 @@ import struct -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeFlag -from yabgp.message.attribute import AttributeID from yabgp.common import constants as bgp_cons from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class Origin(Attribute): diff --git a/yabgp/message/attribute/originatorid.py b/yabgp/message/attribute/originatorid.py index 45b3c131..505b5898 100644 --- a/yabgp/message/attribute/originatorid.py +++ b/yabgp/message/attribute/originatorid.py @@ -18,11 +18,9 @@ import netaddr -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeFlag -from yabgp.message.attribute import AttributeID -from yabgp.common import exception as excep from yabgp.common import constants as bgp_cons +from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class OriginatorID(Attribute): diff --git a/yabgp/message/attribute/pmsitunnel.py b/yabgp/message/attribute/pmsitunnel.py index 19a65c77..2c7e595c 100644 --- a/yabgp/message/attribute/pmsitunnel.py +++ b/yabgp/message/attribute/pmsitunnel.py @@ -13,14 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. -import struct import binascii +import struct + import netaddr -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeFlag -from yabgp.message.attribute import AttributeID from yabgp.common import constants as bgp_cons +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class PMSITunnel(Attribute): diff --git a/yabgp/message/attribute/sr/bgpprefixsid.py b/yabgp/message/attribute/sr/bgpprefixsid.py index d3144705..720f9ee5 100644 --- a/yabgp/message/attribute/sr/bgpprefixsid.py +++ b/yabgp/message/attribute/sr/bgpprefixsid.py @@ -13,9 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. -import struct - import binascii +import struct from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID diff --git a/yabgp/message/attribute/sr/srv6/l3service.py b/yabgp/message/attribute/sr/srv6/l3service.py index c78227c3..19751843 100644 --- a/yabgp/message/attribute/sr/srv6/l3service.py +++ b/yabgp/message/attribute/sr/srv6/l3service.py @@ -13,13 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. -import struct - import binascii +import struct from yabgp.tlv import TLV -from ..bgpprefixsid import BGPPrefixSID +from ..bgpprefixsid import BGPPrefixSID # 2. SRv6 Services TLVs # diff --git a/yabgp/message/attribute/sr/srv6/sidinformation.py b/yabgp/message/attribute/sr/srv6/sidinformation.py index 4fee4579..e51d6ba1 100644 --- a/yabgp/message/attribute/sr/srv6/sidinformation.py +++ b/yabgp/message/attribute/sr/srv6/sidinformation.py @@ -13,14 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. +import binascii import struct -import binascii import netaddr from yabgp.tlv import TLV -from .l3service import SRv6L3Service +from .l3service import SRv6L3Service # 3.1. SRv6 SID Information Sub-TLV # diff --git a/yabgp/message/attribute/sr/srv6/sidstructure.py b/yabgp/message/attribute/sr/srv6/sidstructure.py index b72bd826..30f6743f 100644 --- a/yabgp/message/attribute/sr/srv6/sidstructure.py +++ b/yabgp/message/attribute/sr/srv6/sidstructure.py @@ -14,8 +14,8 @@ # under the License. from yabgp.tlv import TLV -from .sidinformation import SRv6SIDInformation +from .sidinformation import SRv6SIDInformation # 3.2.1. SRv6 SID Structure Sub-Sub-TLV # diff --git a/yabgp/message/attribute/tunnelencaps.py b/yabgp/message/attribute/tunnelencaps.py index eb76ace6..1e2fbbb2 100644 --- a/yabgp/message/attribute/tunnelencaps.py +++ b/yabgp/message/attribute/tunnelencaps.py @@ -20,11 +20,9 @@ import netaddr -from yabgp.message.attribute import Attribute -from yabgp.message.attribute import AttributeFlag -from yabgp.message.attribute import AttributeID from yabgp.common import constants as bgp_cons from yabgp.common import exception as excep +from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID class TunnelEncaps(Attribute): @@ -86,20 +84,24 @@ def construct_weight_and_seg(cls, segment_list): if seg_type == bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_MPLS: value = seg[list(seg)[0]] sum_value = cls.construct_optional_label_sid(value) - seg_hex += struct.pack('!B', bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_MPLS) + struct.pack('!B', 6) + b'\x00\x00' +\ - struct.pack('!I', sum_value) + seg_hex += (struct.pack('!B', bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_MPLS) + + struct.pack('!B', 6) + b'\x00\x00' + + struct.pack('!I', sum_value)) # 3 elif seg_type == bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_IPV4_SID: value = seg[list(seg)[0]] ipv4_node = value['node'] if "SID" not in value.keys(): - seg_hex += struct.pack('!B', bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_IPV4_SID) + struct.pack('!B', 6) + b'\x00\x00' +\ - netaddr.IPAddress(ipv4_node).packed + seg_hex += (struct.pack('!B', bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_IPV4_SID) + + struct.pack('!B', 6) + b'\x00\x00' + + netaddr.IPAddress(ipv4_node).packed) else: opt_sid = value['SID'] sum_value = cls.construct_optional_label_sid(opt_sid) - seg_hex += struct.pack('!B', bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_IPV4_SID) + struct.pack('!B', 10) + b'\x00\x00' +\ - netaddr.IPAddress(ipv4_node).packed + struct.pack('!I', sum_value) + seg_hex += (struct.pack('!B', bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_IPV4_SID) + + struct.pack('!B', 10) + b'\x00\x00' + + netaddr.IPAddress(ipv4_node).packed + + struct.pack('!I', sum_value)) # 5 elif seg_type == bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_IPV4_INDEX_SID: value = seg[list(seg)[0]] @@ -121,19 +123,22 @@ def construct_weight_and_seg(cls, segment_list): local_ipv4 = value['local'] remote_ipv4 = value['remote'] if "SID" not in value.keys(): - seg_hex += struct.pack('!B', bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_IPV4_ADDR_SID) + struct.pack('!B', 10) +\ - b'\x00\x00' + netaddr.IPAddress(local_ipv4).packed + netaddr.IPAddress(remote_ipv4).packed + seg_hex += (struct.pack('!B', bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_IPV4_ADDR_SID) + + struct.pack('!B', 10) + b'\x00\x00' + + netaddr.IPAddress(local_ipv4).packed + + netaddr.IPAddress(remote_ipv4).packed) else: opt_sid = value['SID'] sum_value = cls.construct_optional_label_sid(opt_sid) - seg_hex += struct.pack('!B', bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_IPV4_ADDR_SID) + struct.pack('!B', 14) +\ - b'\x00\x00' + netaddr.IPAddress(local_ipv4).packed + netaddr.IPAddress(remote_ipv4).packed +\ - struct.pack('!I', sum_value) + seg_hex += (struct.pack('!B', bgp_cons.BGP_SRTE_SEGMENT_SUBTLV_IPV4_ADDR_SID) + + struct.pack('!B', 14) + b'\x00\x00' + + netaddr.IPAddress(local_ipv4).packed + + netaddr.IPAddress(remote_ipv4).packed + + struct.pack('!I', sum_value)) return weight_hex, seg_hex @classmethod def construct(cls, value): - """Construct a attribute :param value: python dictionary @@ -168,12 +173,12 @@ def construct(cls, value): """ policy_hex = b'' policy = value - data = dict([(int(l), r) for (l, r) in policy.items()]) + data = dict([(int(k), r) for (k, r) in policy.items()]) policy_value_hex = b'' items = data.keys() if bgp_cons.BGP_BSID_PREFERENCE_OLD_OR_NEW not in items: raise excep.ConstructAttributeFailed( - reason='failed to construct attributes: %s' % 'please provide the value of TLV encoding', + reason='failed to construct attributes: {}'.format('please provide the value of TLV encoding'), data={} ) for type_tmp in items: @@ -188,29 +193,34 @@ def construct(cls, value): # Preference Sub-TLV if bgp_cons.BGPSUB_TLV_PREFERENCE not in items: if bgp_cons.BGPSUB_TLV_PREFERENCE_NEW in items: - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_PREFERENCE) + struct.pack('!B', 6) + \ - b'\x00\x00' + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_PREFERENCE_NEW]) + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_PREFERENCE) + + struct.pack('!B', 6) + b'\x00\x00' + + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_PREFERENCE_NEW])) else: - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_PREFERENCE) + struct.pack('!B', 6) + \ - b'\x00\x00' + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_PREFERENCE]) + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_PREFERENCE) + + struct.pack('!B', 6) + b'\x00\x00' + + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_PREFERENCE])) # Binding SID Sub-TLV if bgp_cons.BGPSUB_TLV_BINDGINGSID not in items: if bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW not in items: - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID) + struct.pack('!B', 2) +\ - b'\x00\x00' + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID) + + struct.pack('!B', 2) + b'\x00\x00') else: - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID) + struct.pack('!B', 6) +\ - b'\x00\x00' + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW] << 12) + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID) + + struct.pack('!B', 6) + b'\x00\x00' + + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW] << 12)) else: - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID) + struct.pack('!B', 6) +\ - b'\x00\x00' + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_BINDGINGSID] << 12) + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID) + + struct.pack('!B', 6) + b'\x00\x00' + + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_BINDGINGSID] << 12)) # new ios elif data[type_tmp] == 'new': # Preference Sub-TLV if bgp_cons.BGPSUB_TLV_PREFERENCE not in items: if bgp_cons.BGPSUB_TLV_PREFERENCE_NEW in items: - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_PREFERENCE_NEW) + struct.pack('!B', 6) + \ - b'\x00\x00' + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_PREFERENCE_NEW]) + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_PREFERENCE_NEW) + + struct.pack('!B', 6) + b'\x00\x00' + + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_PREFERENCE_NEW])) # else: # policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_PREFERENCE_NEW) + \ # struct.pack('!B', 6) + b'\x00\x00' \ @@ -218,27 +228,33 @@ def construct(cls, value): # Binding SID Sub-TLV if bgp_cons.BGPSUB_TLV_BINDGINGSID not in items: if bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW not in items: - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW) + struct.pack('!B', 2) +\ - b'\x00\x00' + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW) + + struct.pack('!B', 2) + b'\x00\x00') else: - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW) + struct.pack('!B', 6) +\ - b'\x00\x00' + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW] << 12) + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW) + + struct.pack('!B', 6) + b'\x00\x00' + + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW] << 12)) else: - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW) + struct.pack('!B', 6) +\ - b'\x00\x00' + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_BINDGINGSID] << 12) + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_BINDGINGSID_NEW) + + struct.pack('!B', 6) + b'\x00\x00' + + struct.pack('!I', data[bgp_cons.BGPSUB_TLV_BINDGINGSID] << 12)) # Explicit NULL Label Policy Sub-TLV if bgp_cons.BGPSUB_TLV_ENLP_NEW in items: - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_ENLP_NEW) + struct.pack('!B', 3) + \ - b'\x00\x00' + struct.pack('!B', data[bgp_cons.BGPSUB_TLV_ENLP_NEW]) + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_ENLP_NEW) + + struct.pack('!B', 3) + b'\x00\x00' + + struct.pack('!B', data[bgp_cons.BGPSUB_TLV_ENLP_NEW])) # Policy Priority Sub-TLV if bgp_cons.BGPSUB_TLV_PRIORITY_NEW in items: - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_PRIORITY_NEW) + struct.pack('!B', 2) + \ - struct.pack('!B', data[bgp_cons.BGPSUB_TLV_PRIORITY_NEW]) + b'\x00' + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_PRIORITY_NEW) + + struct.pack('!B', 2) + + struct.pack('!B', data[bgp_cons.BGPSUB_TLV_PRIORITY_NEW]) + + b'\x00') # Policy Name Sub-TLV if bgp_cons.BGPSUB_TLV_POLICYNAME_NEW in items: length = len(data[bgp_cons.BGPSUB_TLV_POLICYNAME_NEW]) + 1 - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_POLICYNAME_NEW) + struct.pack('!H', length) + \ - b'\x00' + str(data[bgp_cons.BGPSUB_TLV_POLICYNAME_NEW]).encode('ascii') + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_POLICYNAME_NEW) + + struct.pack('!H', length) + b'\x00' + + str(data[bgp_cons.BGPSUB_TLV_POLICYNAME_NEW]).encode('ascii')) # 3.1. The Remote Endpoint Sub-TLV: if bgp_cons.BGPSUB_TLV_REMOTEENDPOINT_NEW in items: asn = data[bgp_cons.BGPSUB_TLV_REMOTEENDPOINT_NEW].get('asn') @@ -252,26 +268,31 @@ def construct(cls, value): af_value = 2 else: raise excep.ConstructAttributeFailed( - reason='failed to construct attributes: %s' % 'remote endpoint address family' - ' is ipv4 or ipv6', + reason='failed to construct attributes: {}'.format('remote endpoint address family' + ' is ipv4 or ipv6'), data={} ) - policy_value_hex += struct.pack('!B', bgp_cons.BGPSUB_TLV_REMOTEENDPOINT_NEW) + struct.pack('!B', length) \ - + struct.pack('!I', asn) + struct.pack('!H', af_value) + netaddr.IPAddress(address).packed + policy_value_hex += (struct.pack('!B', bgp_cons.BGPSUB_TLV_REMOTEENDPOINT_NEW) + + struct.pack('!B', length) + + struct.pack('!I', asn) + + struct.pack('!H', af_value) + + netaddr.IPAddress(address).packed) else: raise excep.ConstructAttributeFailed( - reason='failed to construct attributes: %s' % 'TLV encoding must be one value of new or old', + reason='failed to construct attributes: {}'.format( + 'TLV encoding must be one value of new or old'), data={} ) if type_tmp == bgp_cons.BGPSUB_TLV_SIDLIST: # Sub_TLV segment list seg_list_hex = b'' for seg_list in data[type_tmp]: - segment_list = dict([(int(l), r) for (l, r) in seg_list.items()]) + segment_list = dict([(int(k), r) for (k, r) in seg_list.items()]) weight_hex, seg_hex = cls.construct_weight_and_seg(segment_list) - seg_list_hex += struct.pack('!B', type_tmp) + struct.pack('!H', len(weight_hex) + len(seg_hex) + 1) +\ - b'\x00' + weight_hex + seg_hex + seg_list_hex += (struct.pack('!B', type_tmp) + + struct.pack('!H', len(weight_hex) + len(seg_hex) + 1) + + b'\x00' + weight_hex + seg_hex) policy_value_hex += seg_list_hex policy_hex += struct.pack('!H', bgp_cons.BGP_TUNNEL_ENCAPS_SR_TE_POLICY_TYPE) +\ struct.pack('!H', len(policy_value_hex)) + policy_value_hex diff --git a/yabgp/message/keepalive.py b/yabgp/message/keepalive.py index e07f0688..f7ec877f 100644 --- a/yabgp/message/keepalive.py +++ b/yabgp/message/keepalive.py @@ -17,11 +17,11 @@ import struct -from yabgp.common.exception import MessageHeaderError from yabgp.common.constants import ERR_MSG_HDR_BAD_MSG_LEN +from yabgp.common.exception import MessageHeaderError -class KeepAlive(object): +class KeepAlive: """ KEEPALIVE messages are exchanged between peers often diff --git a/yabgp/message/notification.py b/yabgp/message/notification.py index f1228f7e..e21abfdc 100644 --- a/yabgp/message/notification.py +++ b/yabgp/message/notification.py @@ -18,7 +18,7 @@ import struct -class Notification(object): +class Notification: """ notification diff --git a/yabgp/message/open.py b/yabgp/message/open.py index c2f07e4c..e215b27c 100644 --- a/yabgp/message/open.py +++ b/yabgp/message/open.py @@ -16,14 +16,15 @@ """ BGP Open message""" import struct + import netaddr -from yabgp.common import exception as excp from yabgp.common import constants as bgp_cons +from yabgp.common import exception as excp from yabgp.common.constants import AFI_SAFI_STR_DICT -class Open(object): +class Open: """ After a TCP connection is established, the first message sent by each side is an OPEN message. If the OPEN message is acceptable, a @@ -32,7 +33,6 @@ class Open(object): def __init__(self, version=None, asn=None, hold_time=None, bgp_id=None, opt_para_len=None, opt_paras=None): - """ :param version: BGP Protocol version. :param asn: AS number. @@ -67,14 +67,13 @@ def __init__(self, version=None, asn=None, hold_time=None, # used to store Capabilities {code: value} def parse(self, message): - """Parses a BGP Open message""" try: self.version, self.asn, self.hold_time, \ self.bgp_id, self.opt_para_len = struct.unpack('!BHHIB', message[:10]) - except: + except struct.error: raise excp.MessageHeaderError( sub_error=bgp_cons.ERR_MSG_HDR_BAD_MSG_LEN, data=message[:10]) @@ -223,7 +222,6 @@ def parse(self, message): @staticmethod def construct_header(msg): - """Prepends the mandatory header to a constructed BGP message # 16-octet 2-octet 1-octet #---------------+--------+---------+------+ @@ -233,7 +231,6 @@ def construct_header(msg): return b'\xff' * 16 + struct.pack('!HB', len(msg) + 19, 1) + msg def construct(self, my_capability): - """ Construct a BGP Open message """ capas = b'' # Construct Capabilities Optional Parameter (Parameter Type 2) @@ -275,7 +272,7 @@ def construct(self, my_capability): # ========================================================================== Optional Parameters -class Capability(object): +class Capability: """ The parameter contains one or more triples , where each triple is encoded as @@ -364,7 +361,6 @@ class Capability(object): reserved = range(128, 256) def __init__(self, capa_code=None, capa_length=None, capa_value=None): - """ +------------------------------+ | Capability Code (1 octet) | @@ -380,20 +376,18 @@ def __init__(self, capa_code=None, capa_length=None, capa_value=None): self.capa_value = capa_value def parse(self, message): - """ Partition Capabilities message one by one """ try: self.capa_code, self.capa_length = struct.unpack('!BB', message[:2]) - except: + except struct.error: raise excp.OpenMessageError( sub_error=bgp_cons.ERR_MSG_HDR_BAD_MSG_LEN, data=message[:2]) self.capa_value = message[2:self.capa_length + 2] def construct(self, my_capability=None): - """ Construct a capability PDU """ # for 4 bytes as diff --git a/yabgp/message/route_refresh.py b/yabgp/message/route_refresh.py index a9f42b53..af9a11da 100644 --- a/yabgp/message/route_refresh.py +++ b/yabgp/message/route_refresh.py @@ -18,7 +18,7 @@ import struct -class RouteRefresh(object): +class RouteRefresh: """ Route Refresh message diff --git a/yabgp/message/update.py b/yabgp/message/update.py index 5f1594f2..a7a1456b 100644 --- a/yabgp/message/update.py +++ b/yabgp/message/update.py @@ -17,36 +17,38 @@ import binascii import logging -import netaddr import struct import traceback -from yabgp.common import exception as excep + +import netaddr + from yabgp.common import constants as bgp_cons +from yabgp.common import exception as excep from yabgp.message.attribute import AttributeFlag -from yabgp.message.attribute.origin import Origin +from yabgp.message.attribute.aggregator import Aggregator from yabgp.message.attribute.aspath import ASPath -from yabgp.message.attribute.nexthop import NextHop -from yabgp.message.attribute.med import MED -from yabgp.message.attribute.localpref import LocalPreference from yabgp.message.attribute.atomicaggregate import AtomicAggregate -from yabgp.message.attribute.aggregator import Aggregator -from yabgp.message.attribute.community import Community -from yabgp.message.attribute.originatorid import OriginatorID from yabgp.message.attribute.clusterlist import ClusterList +from yabgp.message.attribute.community import Community +from yabgp.message.attribute.extcommunity import ExtCommunity +from yabgp.message.attribute.largecommunity import LargeCommunity +from yabgp.message.attribute.linkstate.linkstate import LinkState +from yabgp.message.attribute.localpref import LocalPreference +from yabgp.message.attribute.med import MED from yabgp.message.attribute.mpreachnlri import MpReachNLRI from yabgp.message.attribute.mpunreachnlri import MpUnReachNLRI +from yabgp.message.attribute.nexthop import NextHop +from yabgp.message.attribute.nlri.evpn import EVPN +from yabgp.message.attribute.origin import Origin +from yabgp.message.attribute.originatorid import OriginatorID +from yabgp.message.attribute.pmsitunnel import PMSITunnel from yabgp.message.attribute.sr.bgpprefixsid import BGPPrefixSID from yabgp.message.attribute.tunnelencaps import TunnelEncaps -from yabgp.message.attribute.extcommunity import ExtCommunity -from yabgp.message.attribute.pmsitunnel import PMSITunnel -from yabgp.message.attribute.linkstate.linkstate import LinkState -from yabgp.message.attribute.nlri.evpn import EVPN -from yabgp.message.attribute.largecommunity import LargeCommunity LOG = logging.getLogger() -class Update(object): +class Update: """ An UPDATE message is used to advertise feasible routes that share common path attributes to a peer, or to withdraw multiple unfeasible @@ -268,7 +270,7 @@ def parse_prefix_list(data, addpath=False): if remainder > 0: prefix_data[-1] &= 255 << (8 - remainder) prefix_data = prefix_data + list(str(0)) * 4 - prefix = "%s.%s.%s.%s" % (tuple(prefix_data[0:4])) + '/' + str(prefix_len) + prefix = "{}.{}.{}.{}".format(*tuple(prefix_data[0:4])) + '/' + str(prefix_len) if not addpath: prefixes.append(prefix) else: diff --git a/yabgp/net/__init__.py b/yabgp/net/__init__.py index 28fb315c..fc529388 100644 --- a/yabgp/net/__init__.py +++ b/yabgp/net/__init__.py @@ -16,7 +16,7 @@ import socket -class IPAddress(object): +class IPAddress: @staticmethod def unpack(data): @@ -27,5 +27,5 @@ def pack(data): return socket.inet_pton(socket.AF_INET if len(data.split('.')) == 4 else socket.AF_INET6, data) -class IPNetwork(object): +class IPNetwork: pass diff --git a/yabgp/tests/unit/api/test_app.py b/yabgp/tests/unit/api/test_app.py index 6052ee46..1c3d82c7 100644 --- a/yabgp/tests/unit/api/test_app.py +++ b/yabgp/tests/unit/api/test_app.py @@ -16,6 +16,7 @@ """Test app""" import unittest + from yabgp.api.app import app diff --git a/yabgp/tests/unit/api/test_v1.py b/yabgp/tests/unit/api/test_v1.py index d617c58c..7dd01a80 100644 --- a/yabgp/tests/unit/api/test_v1.py +++ b/yabgp/tests/unit/api/test_v1.py @@ -16,6 +16,7 @@ """Test v1 api""" import unittest + from yabgp.api.app import app diff --git a/yabgp/tests/unit/message/attribute/linkstate/node/test_flex_algo_define.py b/yabgp/tests/unit/message/attribute/linkstate/node/test_flex_algo_define.py index 2fa9f5fa..0d9a9646 100644 --- a/yabgp/tests/unit/message/attribute/linkstate/node/test_flex_algo_define.py +++ b/yabgp/tests/unit/message/attribute/linkstate/node/test_flex_algo_define.py @@ -18,12 +18,12 @@ import unittest from yabgp.message.attribute.linkstate.node.flex_algo_define import ( - FlexAlgorithmDefine, + FlexAlgoDefinitionFlags, FlexAlgoExcludeAdminGroup, - FlexAlgoIncludeAnyAdminGroup, + FlexAlgoExcludeSRLG, FlexAlgoIncludeAllAdminGroup, - FlexAlgoDefinitionFlags, - FlexAlgoExcludeSRLG + FlexAlgoIncludeAnyAdminGroup, + FlexAlgorithmDefine, ) diff --git a/yabgp/tests/unit/message/attribute/linkstate/node/test_node_msd.py b/yabgp/tests/unit/message/attribute/linkstate/node/test_node_msd.py index c1cdae48..dad872ce 100644 --- a/yabgp/tests/unit/message/attribute/linkstate/node/test_node_msd.py +++ b/yabgp/tests/unit/message/attribute/linkstate/node/test_node_msd.py @@ -1,4 +1,5 @@ import unittest + from yabgp.message.attribute.linkstate.node.node_msd import NodeMSD_266 diff --git a/yabgp/tests/unit/message/attribute/nlri/test_linkdelay.py b/yabgp/tests/unit/message/attribute/nlri/test_linkdelay.py index da87d4db..35c0c7de 100644 --- a/yabgp/tests/unit/message/attribute/nlri/test_linkdelay.py +++ b/yabgp/tests/unit/message/attribute/nlri/test_linkdelay.py @@ -16,6 +16,7 @@ """ Test Link State attribute""" import unittest + from yabgp.message.attribute.linkstate.linkstate import LinkState diff --git a/yabgp/tests/unit/message/attribute/nlri/test_linkstate_prefix_sid.py b/yabgp/tests/unit/message/attribute/nlri/test_linkstate_prefix_sid.py index d40483f6..f8750f13 100644 --- a/yabgp/tests/unit/message/attribute/nlri/test_linkstate_prefix_sid.py +++ b/yabgp/tests/unit/message/attribute/nlri/test_linkstate_prefix_sid.py @@ -16,6 +16,7 @@ """ Test Link State attribute""" import unittest + from yabgp.message.attribute.linkstate.linkstate import LinkState diff --git a/yabgp/tests/unit/message/attribute/test_add_path.py b/yabgp/tests/unit/message/attribute/test_add_path.py index 4abdea42..09a75237 100644 --- a/yabgp/tests/unit/message/attribute/test_add_path.py +++ b/yabgp/tests/unit/message/attribute/test_add_path.py @@ -16,6 +16,7 @@ """Test Add Path Capability""" import unittest + from yabgp.message.open import convert_addpath_str_to_int diff --git a/yabgp/tests/unit/message/attribute/test_aspath.py b/yabgp/tests/unit/message/attribute/test_aspath.py index c7996f61..f53b7016 100644 --- a/yabgp/tests/unit/message/attribute/test_aspath.py +++ b/yabgp/tests/unit/message/attribute/test_aspath.py @@ -17,9 +17,8 @@ import unittest +from yabgp.common.constants import ERR_MSG_UPDATE_ATTR_LEN, ERR_MSG_UPDATE_MALFORMED_ASPATH from yabgp.common.exception import UpdateMessageError -from yabgp.common.constants import ERR_MSG_UPDATE_MALFORMED_ASPATH -from yabgp.common.constants import ERR_MSG_UPDATE_ATTR_LEN from yabgp.message.attribute.aspath import ASPath diff --git a/yabgp/tests/unit/message/attribute/test_atomicaggregate.py b/yabgp/tests/unit/message/attribute/test_atomicaggregate.py index cac43f51..2f85b307 100644 --- a/yabgp/tests/unit/message/attribute/test_atomicaggregate.py +++ b/yabgp/tests/unit/message/attribute/test_atomicaggregate.py @@ -17,8 +17,8 @@ import unittest -from yabgp.common.exception import UpdateMessageError from yabgp.common.constants import ERR_MSG_UPDATE_OPTIONAL_ATTR +from yabgp.common.exception import UpdateMessageError from yabgp.message.attribute.atomicaggregate import AtomicAggregate diff --git a/yabgp/tests/unit/message/attribute/test_clusterlist.py b/yabgp/tests/unit/message/attribute/test_clusterlist.py index 46adf345..02213393 100644 --- a/yabgp/tests/unit/message/attribute/test_clusterlist.py +++ b/yabgp/tests/unit/message/attribute/test_clusterlist.py @@ -17,8 +17,8 @@ import unittest -from yabgp.common.constants import ERR_MSG_UPDATE_ATTR_LEN from yabgp.common import exception as excep +from yabgp.common.constants import ERR_MSG_UPDATE_ATTR_LEN from yabgp.message.attribute.clusterlist import ClusterList diff --git a/yabgp/tests/unit/message/attribute/test_extcommunity.py b/yabgp/tests/unit/message/attribute/test_extcommunity.py index 50ec5e4a..88ae74f0 100644 --- a/yabgp/tests/unit/message/attribute/test_extcommunity.py +++ b/yabgp/tests/unit/message/attribute/test_extcommunity.py @@ -27,7 +27,7 @@ class TestExtCommunity(unittest.TestCase): def test_parse_rt0(self): # Route Target,Format AS(2bytes):AN(4bytes) ext_community = ExtCommunity.parse(value=b'\x00\x02\x00\x64\x00\x00\x00\x0c') - self.assertEqual(['%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RT_0], '100:12')], + self.assertEqual(['{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RT_0], '100:12')], ext_community) def test_construct_rt0(self): @@ -37,7 +37,7 @@ def test_construct_rt0(self): def test_parse_rt1(self): # Route Target,Format IPv4 address(4bytes):AN(2bytes) ext_community = ExtCommunity.parse(value=b'\x01\x02\x0a\x0a\x0a\x0a\x00\x0c') - self.assertEqual(['%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RT_1], '10.10.10.10:12')], + self.assertEqual(['{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RT_1], '10.10.10.10:12')], ext_community) def test_construct_rt1(self): @@ -47,7 +47,7 @@ def test_construct_rt1(self): def test_parse_rt2(self): # Route Target,Format AS(4bytes):AN(2bytes) ext_community = ExtCommunity.parse(value=b'\x02\x02\x00\x01\x00\x01\x00\x0c') - self.assertEqual(['%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RT_2], '65537:12')], + self.assertEqual(['{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RT_2], '65537:12')], ext_community) def test_construct_rt2(self): @@ -57,7 +57,7 @@ def test_construct_rt2(self): def test_parse_ro0(self): # Route Origin,Format AS(2bytes):AN(4bytes) ext_community = ExtCommunity.parse(value=b'\x00\x03\x00\x64\x00\x00\x00\x0c') - self.assertEqual(['%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RO_0], '100:12')], + self.assertEqual(['{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RO_0], '100:12')], ext_community) def test_construct_ro0(self): @@ -67,7 +67,7 @@ def test_construct_ro0(self): def test_parse_ro1(self): # Route Origin,Format IPv4 address(4bytes):AN(2bytes) ext_community = ExtCommunity.parse(value=b'\x01\x03\x0a\x0a\x0a\x0a\x00\x0c') - self.assertEqual(['%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RO_1], '10.10.10.10:12')], + self.assertEqual(['{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RO_1], '10.10.10.10:12')], ext_community) def test_construct_ro1(self): @@ -77,7 +77,7 @@ def test_construct_ro1(self): def test_parse_ro2(self): # Route Origin,Format AS(4bytes):AN(2bytes) ext_community = ExtCommunity.parse(value=b'\x02\x03\x00\x01\x00\x01\x00\x0c') - self.assertEqual(['%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RO_2], '65537:12')], + self.assertEqual(['{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_RO_2], '65537:12')], ext_community) def test_construct_ro2(self): @@ -101,26 +101,26 @@ def test_parse_unknow(self): def test_parse_construct_flowspec_redirect_vrf(self): community_list = [[bgp_cons.BGP_EXT_REDIRECT_VRF, '4837:100']] community_hex = b'\x80\x08\x12\xe5\x00\x00\x00d' - expected_value = ['%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_REDIRECT_VRF], '4837:100')] + expected_value = ['{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_REDIRECT_VRF], '4837:100')] self.assertEqual(b'\xc0\x10\x08\x80\x08\x12\xe5\x00\x00\x00d', ExtCommunity.construct(value=community_list)) self.assertEqual(expected_value, ExtCommunity.parse(value=community_hex)) def test_parse_construct_flowspec_redirect_nh(self): community_list = [[bgp_cons.BGP_EXT_REDIRECT_NH, '0.0.0.0', 0]] - expected_value = ['%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_REDIRECT_NH], '0.0.0.0', 0)] + expected_value = ['{}:{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_REDIRECT_NH], '0.0.0.0', 0)] self.assertEqual(b'\xc0\x10\x08\x08\x00\x00\x00\x00\x00\x00\x00', ExtCommunity.construct(value=community_list)) self.assertEqual(expected_value, ExtCommunity.parse(value=b'\x08\x00\x00\x00\x00\x00\x00\x00')) def test_parse_construct_tarffic_rate(self): community_list = [[bgp_cons.BGP_EXT_TRA_RATE, "100:6250000"]] - expected_value = ['%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_TRA_RATE], '100:6250000')] + expected_value = ['{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_TRA_RATE], '100:6250000')] self.assertEqual(b'\xc0\x10\x08\x80\x06\x00dJ\xbe\xbc ', ExtCommunity.construct(value=community_list)) self.assertEqual(expected_value, ExtCommunity.parse(value=b'\x80\x06\x00dJ\xbe\xbc ')) def test_parse_construct_transitive_opaque_encap(self): community_list = [[bgp_cons.BGP_EXT_COM_ENCAP, 8]] community_hex = b'\x03\x0c\x00\x00\x00\x00\x00\x08' - expected_value = ['%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_ENCAP], 8)] + expected_value = [f'{bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_ENCAP]}:{8}'] self.assertEqual(expected_value, ExtCommunity.parse(community_hex)) self.assertEqual(community_hex, ExtCommunity.construct(community_list)[3:]) @@ -133,7 +133,7 @@ def test_parse_construct_es_import(self): community_list = [[bgp_cons.BGP_EXT_COM_EVPN_ES_IMPORT, '00-11-22-33-44-55']] community_hex = b'\x06\x02\x00\x11\x22\x33\x44\x55' expected_value = [ - '%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_EVPN_ES_IMPORT], '00-11-22-33-44-55') + '{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_EVPN_ES_IMPORT], '00-11-22-33-44-55') ] self.assertEqual(expected_value, ExtCommunity.parse(community_hex)) self.assertEqual(community_hex, ExtCommunity.construct(community_list)[3:]) @@ -141,21 +141,21 @@ def test_parse_construct_es_import(self): def test_parse_construct_els_label(self): community_list = [[bgp_cons.BGP_EXT_COM_EVPN_ESI_MPLS_LABEL, 1, 20]] community_hex = b'\x06\x01\x01\x00\x00\x00\x01\x41' - expected_value = ['%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_EVPN_ESI_MPLS_LABEL], 1, 20)] + expected_value = [f'{bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_EVPN_ESI_MPLS_LABEL]}:{1}:{20}'] self.assertEqual(community_hex, ExtCommunity.construct(community_list)[3:]) self.assertEqual(expected_value, ExtCommunity.parse(community_hex)) def test_parse_construct_mac_mobil(self): community_list = [[bgp_cons.BGP_EXT_COM_EVPN_MAC_MOBIL, 1, 500]] community_hex = b'\x06\x00\x01\x00\x00\x00\x01\xf4' - expected_value = ['%s:%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_EVPN_MAC_MOBIL], 1, 500)] + expected_value = [f'{bgp_cons.BGP_EXT_COM_STR_DICT[bgp_cons.BGP_EXT_COM_EVPN_MAC_MOBIL]}:{1}:{500}'] self.assertEqual(community_hex, ExtCommunity.construct(community_list)[3:]) self.assertEqual(expected_value, ExtCommunity.parse(community_hex)) def test_parse_construct_evpn_route_mac(self): comunity_list = [[1539, '74-A0-2F-DE-FE-FB']] community_hex = b'\x06\x03\x74\xa0\x2f\xde\xfe\xfb' - expected_value = ['%s:%s' % (bgp_cons.BGP_EXT_COM_STR_DICT[1539], '74-A0-2F-DE-FE-FB')] + expected_value = ['{}:{}'.format(bgp_cons.BGP_EXT_COM_STR_DICT[1539], '74-A0-2F-DE-FE-FB')] self.assertEqual(expected_value, ExtCommunity.parse(community_hex)) self.assertEqual(community_hex, ExtCommunity.construct(comunity_list)[3:]) diff --git a/yabgp/tests/unit/message/attribute/test_localpref.py b/yabgp/tests/unit/message/attribute/test_localpref.py index f8f14e96..f1ce71da 100644 --- a/yabgp/tests/unit/message/attribute/test_localpref.py +++ b/yabgp/tests/unit/message/attribute/test_localpref.py @@ -18,8 +18,8 @@ import unittest -from yabgp.common.exception import UpdateMessageError from yabgp.common.constants import ERR_MSG_UPDATE_ATTR_LEN +from yabgp.common.exception import UpdateMessageError from yabgp.message.attribute.localpref import LocalPreference diff --git a/yabgp/tests/unit/message/attribute/test_med.py b/yabgp/tests/unit/message/attribute/test_med.py index ed858435..3ddce145 100644 --- a/yabgp/tests/unit/message/attribute/test_med.py +++ b/yabgp/tests/unit/message/attribute/test_med.py @@ -18,8 +18,8 @@ import unittest -from yabgp.common.exception import UpdateMessageError from yabgp.common.constants import ERR_MSG_UPDATE_ATTR_LEN +from yabgp.common.exception import UpdateMessageError from yabgp.message.attribute.med import MED diff --git a/yabgp/tests/unit/message/attribute/test_mpreachnlri.py b/yabgp/tests/unit/message/attribute/test_mpreachnlri.py index 99f3e1dc..34c6c09e 100644 --- a/yabgp/tests/unit/message/attribute/test_mpreachnlri.py +++ b/yabgp/tests/unit/message/attribute/test_mpreachnlri.py @@ -16,6 +16,7 @@ """ Unittest for MPReach NLRI""" import unittest + from yabgp.message.attribute.mpreachnlri import MpReachNLRI diff --git a/yabgp/tests/unit/message/attribute/test_mpunreachnlri.py b/yabgp/tests/unit/message/attribute/test_mpunreachnlri.py index a12d8e86..2b5d76b8 100644 --- a/yabgp/tests/unit/message/attribute/test_mpunreachnlri.py +++ b/yabgp/tests/unit/message/attribute/test_mpunreachnlri.py @@ -16,6 +16,7 @@ """ Unittest for MPUnReach NLRI""" import unittest + from yabgp.message.attribute.mpunreachnlri import MpUnReachNLRI diff --git a/yabgp/tests/unit/message/attribute/test_nexthop.py b/yabgp/tests/unit/message/attribute/test_nexthop.py index 98f77796..fe982177 100644 --- a/yabgp/tests/unit/message/attribute/test_nexthop.py +++ b/yabgp/tests/unit/message/attribute/test_nexthop.py @@ -18,9 +18,8 @@ import unittest +from yabgp.common.constants import ERR_MSG_UPDATE_ATTR_LEN, ERR_MSG_UPDATE_INVALID_NEXTHOP from yabgp.common.exception import UpdateMessageError -from yabgp.common.constants import ERR_MSG_UPDATE_ATTR_LEN -from yabgp.common.constants import ERR_MSG_UPDATE_INVALID_NEXTHOP from yabgp.message.attribute.nexthop import NextHop diff --git a/yabgp/tests/unit/message/attribute/test_origin.py b/yabgp/tests/unit/message/attribute/test_origin.py index feb82477..5faed773 100644 --- a/yabgp/tests/unit/message/attribute/test_origin.py +++ b/yabgp/tests/unit/message/attribute/test_origin.py @@ -17,8 +17,8 @@ import unittest -from yabgp.common.exception import UpdateMessageError from yabgp.common.constants import ERR_MSG_UPDATE_INVALID_ORIGIN +from yabgp.common.exception import UpdateMessageError from yabgp.message.attribute.origin import Origin diff --git a/yabgp/tests/unit/message/attribute/test_originatorid.py b/yabgp/tests/unit/message/attribute/test_originatorid.py index 3846aef5..4f9cd6b5 100644 --- a/yabgp/tests/unit/message/attribute/test_originatorid.py +++ b/yabgp/tests/unit/message/attribute/test_originatorid.py @@ -18,8 +18,8 @@ import unittest -from yabgp.common.constants import ERR_MSG_UPDATE_ATTR_LEN from yabgp.common import exception as excep +from yabgp.common.constants import ERR_MSG_UPDATE_ATTR_LEN from yabgp.message.attribute.originatorid import OriginatorID diff --git a/yabgp/tests/unit/message/attribute/test_tunnelencaps.py b/yabgp/tests/unit/message/attribute/test_tunnelencaps.py index 16c40f09..8b7562bc 100644 --- a/yabgp/tests/unit/message/attribute/test_tunnelencaps.py +++ b/yabgp/tests/unit/message/attribute/test_tunnelencaps.py @@ -32,7 +32,7 @@ def test_construct_optional_label_sid(self): def test_construct_weight(self): data_dict = {"9": 10, "1": []} - segment_list = dict([(int(l), r) for (l, r) in data_dict.items()]) + segment_list = dict([(int(k), r) for (k, r) in data_dict.items()]) weight_hex = b'\x09\x06\x00\x00\x00\x00\x00\x0a' sid_hex = b'' self.assertEqual((weight_hex, sid_hex), TunnelEncaps.construct_weight_and_seg(segment_list)) @@ -58,7 +58,7 @@ def test_construct_seg(self): } ] } - segment_list = dict([(int(l), r) for (l, r) in data_dict.items()]) + segment_list = dict([(int(k), r) for (k, r) in data_dict.items()]) weight_hex = b'' sid_hex = b'\x01\x06\x00\x00\x00\x7d\x00\xff\x03\x0a\x00\x00\x0a\x01\x01\x01\x00\x7d\x00\xff' self.assertEqual((weight_hex, sid_hex), TunnelEncaps.construct_weight_and_seg(segment_list)) diff --git a/yabgp/tests/unit/message/test_keepalive.py b/yabgp/tests/unit/message/test_keepalive.py index 26523f44..7e3c4d9f 100644 --- a/yabgp/tests/unit/message/test_keepalive.py +++ b/yabgp/tests/unit/message/test_keepalive.py @@ -17,8 +17,8 @@ import unittest -from yabgp.message.keepalive import KeepAlive from yabgp.common.exception import MessageHeaderError +from yabgp.message.keepalive import KeepAlive class TestKeepAlive(unittest.TestCase): diff --git a/yabgp/tests/unit/message/test_notification.py b/yabgp/tests/unit/message/test_notification.py index ac58b4f4..ff93ce2e 100644 --- a/yabgp/tests/unit/message/test_notification.py +++ b/yabgp/tests/unit/message/test_notification.py @@ -17,7 +17,6 @@ import unittest - from yabgp.message.notification import Notification diff --git a/yabgp/tests/unit/message/test_open.py b/yabgp/tests/unit/message/test_open.py index 85585aef..0e8b159e 100644 --- a/yabgp/tests/unit/message/test_open.py +++ b/yabgp/tests/unit/message/test_open.py @@ -16,10 +16,11 @@ """ Test Open message""" import unittest + import netaddr + +from yabgp.common.constants import HDR_LEN, VERSION from yabgp.message.open import Open -from yabgp.common.constants import VERSION -from yabgp.common.constants import HDR_LEN class TestOpen(unittest.TestCase): diff --git a/yabgp/tests/unit/message/test_route_refresh.py b/yabgp/tests/unit/message/test_route_refresh.py index 35daa417..cdf4e4a6 100644 --- a/yabgp/tests/unit/message/test_route_refresh.py +++ b/yabgp/tests/unit/message/test_route_refresh.py @@ -16,9 +16,9 @@ """ Test Route Refresh message""" import unittest + +from yabgp.common.constants import MSG_CISCOROUTEREFRESH, MSG_ROUTEREFRESH from yabgp.message.route_refresh import RouteRefresh -from yabgp.common.constants import MSG_CISCOROUTEREFRESH -from yabgp.common.constants import MSG_ROUTEREFRESH class TestRouteRefresh(unittest.TestCase): diff --git a/yabgp/tlv.py b/yabgp/tlv.py index 0bcbe202..de495f54 100644 --- a/yabgp/tlv.py +++ b/yabgp/tlv.py @@ -16,7 +16,7 @@ import binascii -class TLV(object): +class TLV: """TLV basic class """ TYPE = -1 @@ -28,7 +28,7 @@ def __init__(self, value): self.value = value def __str__(self): - return '%s: %s' % (self.TYPE_STR, self.value) + return f'{self.TYPE_STR}: {self.value}' @classmethod def unpack(cls, value):