├── src └── poetry_plugin_freeze │ ├── __init__.py │ └── app.py ├── tests ├── fixtures │ ├── nested_packages │ │ ├── app_b │ │ │ └── __init__.py │ │ ├── others │ │ │ ├── app_c │ │ │ │ ├── app_c │ │ │ │ │ └── __init__.py │ │ │ │ ├── dist │ │ │ │ │ └── app_c-0.2-py3-none-any.whl │ │ │ │ ├── pyproject.toml │ │ │ │ └── poetry.lock │ │ │ ├── app_no_deps │ │ │ │ ├── app_no_deps │ │ │ │ │ └── __init__.py │ │ │ │ ├── dist │ │ │ │ │ └── app_no_deps-0.2-py3-none-any.whl │ │ │ │ ├── pyproject.toml │ │ │ │ └── poetry.lock │ │ │ └── app_with_extras │ │ │ │ ├── app_with_extras │ │ │ │ └── __init__.py │ │ │ │ ├── README.md │ │ │ │ ├── dist │ │ │ │ └── app_with_extras-0.1.0-py3-none-any.whl │ │ │ │ ├── pyproject.toml │ │ │ │ └── poetry.lock │ │ ├── dist │ │ │ └── app_b-0.1-py3-none-any.whl │ │ ├── pyproject.toml │ │ └── poetry.lock │ ├── non_poetry_package │ │ └── pyproject.toml │ └── whl_info │ │ ├── METADATA │ │ └── RECORD ├── conftest.py └── test_freeze.py ├── .gitignore ├── Makefile ├── pyproject.toml ├── .github └── workflows │ ├── release.yml │ └── ci.yml ├── README.md └── LICENSE /src/poetry_plugin_freeze/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/app_b/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_c/app_c/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_no_deps/app_no_deps/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | __pycache__ 3 | .ruff_cache 4 | .python-version 5 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_with_extras/app_with_extras/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_with_extras/README.md: -------------------------------------------------------------------------------- 1 | # app with extras 2 | This is a fixture for testing how we freeze extra dependencies. 3 | -------------------------------------------------------------------------------- /tests/fixtures/non_poetry_package/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "non-poetry-package" 3 | readme = "README.md" 4 | requires-python = ">=3.12" 5 | dependencies = [] 6 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/dist/app_b-0.1-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloud-custodian/poetry-plugin-freeze/HEAD/tests/fixtures/nested_packages/dist/app_b-0.1-py3-none-any.whl -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | lint: 4 | black --check src tests 5 | ruff check src tests 6 | 7 | format: 8 | ruff format src tests 9 | 10 | test: 11 | pytest --cov poetry_plugin_freeze tests 12 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_c/dist/app_c-0.2-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloud-custodian/poetry-plugin-freeze/HEAD/tests/fixtures/nested_packages/others/app_c/dist/app_c-0.2-py3-none-any.whl -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_no_deps/dist/app_no_deps-0.2-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloud-custodian/poetry-plugin-freeze/HEAD/tests/fixtures/nested_packages/others/app_no_deps/dist/app_no_deps-0.2-py3-none-any.whl -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_with_extras/dist/app_with_extras-0.1.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloud-custodian/poetry-plugin-freeze/HEAD/tests/fixtures/nested_packages/others/app_with_extras/dist/app_with_extras-0.1.0-py3-none-any.whl -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | import shutil 5 | 6 | 7 | @pytest.fixture 8 | def fixture_root() -> Path: 9 | return Path(__file__).parent / "fixtures" 10 | 11 | 12 | @pytest.fixture 13 | def fixture_copy(tmp_path): 14 | def copy(path): 15 | shutil.copytree(path, tmp_path / path.name) 16 | return tmp_path / path.name 17 | 18 | return copy 19 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_no_deps/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "app_no_deps" 3 | description = "lorem ipsum" 4 | version = "0.2" 5 | authors = ["SideCars"] 6 | license = "Apache-2.0" 7 | packages = [ 8 | { include = "app_no_deps" }] 9 | 10 | [tool.poetry.dependencies] 11 | python = "^3.10" 12 | 13 | [tool.poetry.group.dev.dependencies] 14 | app_b = {path = "../..", develop = true} 15 | 16 | [build-system] 17 | requires = ["poetry>=0.12", "setuptools"] 18 | build-backend = "poetry.masonry.api" 19 | 20 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "app_b" 3 | description = "lorem ipsum" 4 | version = "0.1" 5 | authors = ["SideCars"] 6 | license = "Apache-2.0" 7 | packages = [ 8 | { include = "app_b" }] 9 | classifiers=[ 10 | "License :: OSI Approved :: Apache Software License"] 11 | 12 | 13 | [tool.poetry.dependencies] 14 | python = "^3.8" 15 | pytest = "^7.1" 16 | ruff = "^0.0.259" 17 | 18 | [build-system] 19 | requires = ["poetry>=0.12", "setuptools"] 20 | build-backend = "poetry.masonry.api" 21 | 22 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_c/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "app_c" 3 | description = "lorem ipsum" 4 | version = "0.2" 5 | authors = ["SideCars"] 6 | license = "Apache-2.0" 7 | packages = [ 8 | { include = "app_c" }] 9 | 10 | [tool.poetry.dependencies] 11 | python = "^3.10" 12 | pytest = "^7.1" 13 | pytest-cov = "^4.0" 14 | urllib3 = [ 15 | { version = "<2", python = "<3.11" }, 16 | { version = "*", python = ">=3.11" } 17 | ] 18 | 19 | [tool.poetry.group.dev.dependencies] 20 | app_b = {path = "../..", develop = true} 21 | 22 | [build-system] 23 | requires = ["poetry>=0.12", "setuptools"] 24 | build-backend = "poetry.masonry.api" 25 | 26 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_with_extras/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "app-with-extras" 3 | version = "0.1.0" 4 | description = "App with optional/extra dependencies" 5 | authors = ["A. LaCarte"] 6 | license = "Apache-2.0" 7 | readme = "README.md" 8 | packages = [{include = "app_with_extras"}] 9 | 10 | [tool.poetry.dependencies] 11 | # Base dependencies 12 | python = "^3.10" 13 | ruff = "^0.0.259" 14 | 15 | # Optional dependencies 16 | app-c = {path = "../app_c", optional = true, develop = true} 17 | app-b = {path = "../..", optional = true} 18 | coverage = {version = "*", optional = true, extras = ["toml"]} 19 | 20 | [tool.poetry.extras] 21 | bells = ["app-c"] 22 | whistles = ["coverage", "app-b"] 23 | 24 | [build-system] 25 | requires = ["poetry-core"] 26 | build-backend = "poetry.core.masonry.api" 27 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "poetry-plugin-freeze" 3 | version = "1.2.0" 4 | description = "Poetry plugin to freeze a wheel's dependencies per lock file" 5 | license = "Apache-2.0" 6 | authors = ["Kapil Thangavelu "] 7 | readme = "README.md" 8 | homepage = "https://github.com/cloud-custodian/poetry-plugin-freeze" 9 | 10 | packages = [ 11 | { include = "poetry_plugin_freeze", from = "src" } 12 | ] 13 | 14 | include = [ 15 | { path = "tests", format = "sdist" } 16 | ] 17 | 18 | [tool.poetry.dependencies] 19 | python = "^3.9" 20 | poetry = ">=1.3.0" 21 | poetry-core = ">=1.3.0" 22 | poetry-plugin-export = "^1.8.0" 23 | 24 | 25 | [tool.poetry.group.dev.dependencies] 26 | pytest = "^7.1" 27 | pytest-cov = "^4.0" 28 | pytest-xdist = { version = "^3.1", extras = ["psutil"] } 29 | black = "*" 30 | ruff = "^0.8" 31 | 32 | 33 | [tool.poetry.plugins."poetry.application.plugin"] 34 | freeze-wheel = "poetry_plugin_freeze.app:FreezeApplicationPlugin" 35 | 36 | [tool.ruff] 37 | line-length = 100 38 | 39 | [tool.black] 40 | line-length = 100 41 | 42 | [build-system] 43 | requires = ["poetry-core"] 44 | build-backend = "poetry.core.masonry.api" 45 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | workflow_dispatch: 7 | inputs: {} 8 | 9 | jobs: 10 | Upload: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Install poetry 17 | run: pipx install poetry 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: "3.11" 23 | cache: "poetry" 24 | 25 | - name: Install dependencies 26 | run: poetry install 27 | 28 | - name: Build 29 | run: | 30 | poetry build 31 | sha256sum dist/* 32 | 33 | - name: Create Release 34 | uses: ncipollo/release-action@v1 35 | if: startsWith(github.ref, 'refs/tags/') 36 | with: 37 | artifacts: "dist/*" 38 | token: ${{ secrets.GITHUB_TOKEN }} 39 | draft: false 40 | generateReleaseNotes: true 41 | 42 | - name: Publish 43 | if: startsWith(github.ref, 'refs/tags/') 44 | env: 45 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} 46 | run: | 47 | poetry publish 48 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | Lint: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-python@v5 19 | with: 20 | python-version: "3.13" 21 | - name: Linting 22 | run: | 23 | pip install black ruff 24 | make lint 25 | 26 | Tests: 27 | needs: Lint 28 | runs-on: ubuntu-latest 29 | strategy: 30 | matrix: 31 | python-version: ["3.9", "3.12", "3.13"] 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v4 35 | with: 36 | fetch-depth: 2 37 | 38 | - name: Install poetry 39 | run: pipx install poetry 40 | 41 | - name: Set up Python ${{ matrix.python-version }} 42 | uses: actions/setup-python@v5 43 | with: 44 | python-version: "${{ matrix.python-version }}" 45 | cache: "poetry" 46 | 47 | - name: Install dependencies 48 | run: poetry install 49 | 50 | - name: Coverage run pytest 51 | run: | 52 | poetry run coverage run --source poetry_plugin_freeze -m pytest tests 53 | poetry run coverage xml 54 | 55 | - name: Upload Code Coverage 56 | uses: codecov/codecov-action@v5 57 | if: contains(matrix.python-version, '3.13') && contains(matrix.os, 'ubuntu') 58 | with: 59 | files: ./coverage.xml 60 | name: codecov 61 | -------------------------------------------------------------------------------- /tests/fixtures/whl_info/METADATA: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: c7n-gcp 3 | Version: 0.4.22 4 | Summary: Cloud Custodian - Google Cloud Provider 5 | Home-page: https://cloudcustodian.io 6 | License: Apache-2.0 7 | Author: Cloud Custodian Project 8 | Requires-Python: >=3.7,<4.0 9 | Classifier: License :: OSI Approved :: Apache Software License 10 | Classifier: Programming Language :: Python :: 3 11 | Classifier: Programming Language :: Python :: 3.7 12 | Classifier: Programming Language :: Python :: 3.8 13 | Classifier: Programming Language :: Python :: 3.9 14 | Classifier: Programming Language :: Python :: 3.10 15 | Classifier: Programming Language :: Python :: 3.11 16 | Classifier: Topic :: System :: Distributed Computing 17 | Classifier: Topic :: System :: Systems Administration 18 | Requires-Dist: google-api-core (==2.10.2) 19 | Requires-Dist: google-api-python-client (>=2.0,<3.0) 20 | Requires-Dist: google-auth (>=2.1.0,<3.0.0) 21 | Requires-Dist: google-cloud-logging (>=3.2,<4.0) 22 | Requires-Dist: google-cloud-monitoring (>=2.5.0,<3.0.0) 23 | Requires-Dist: google-cloud-storage (>=1.42.2,<2.0.0) 24 | Requires-Dist: grpcio (==1.49.1) 25 | Requires-Dist: pyrate-limiter (>=2.8.4,<3.0.0) 26 | Requires-Dist: retrying (>=1.3.3,<2.0.0) 27 | Project-URL: Documentation, https://cloudcustodian.io/docs/ 28 | Project-URL: Repository, https://github.com/cloud-custodian/cloud-custodian 29 | Description-Content-Type: text/markdown 30 | 31 | # Custodian GCP Support 32 | 33 | Status - Alpha 34 | 35 | # Features 36 | 37 | - Serverless ✅ 38 | - Api Subscriber ✅ 39 | - Metrics ✅ 40 | - Resource Query ✅ 41 | - Multi Account (c7n-org) ✅ 42 | 43 | # Getting Started 44 | 45 | 46 | ## via pip 47 | 48 | ``` 49 | pip install c7n_gcp 50 | ``` 51 | 52 | By default custodian will use credentials associated to the gcloud cli, which will generate 53 | warnings per google.auth (https://github.com/googleapis/google-auth-library-python/issues/292) 54 | 55 | The recommended authentication form for production usage is to create a service account and 56 | credentials, which will be picked up via by the custodian cli via setting the 57 | *GOOGLE_APPLICATION_CREDENTIALS* environment variable. 58 | 59 | 60 | # Serverless 61 | 62 | Custodian supports both periodic and api call events for serverless 63 | policy execution. 64 | 65 | GCP Cloud Functions require cloudbuild api be enabled on the project 66 | the functions are deployed to. 67 | 68 | Periodic execution mode also requires cloudscheduler api be enabled on 69 | a project. Cloudscheduler usage also requires an app engine instance 70 | in the same region as the function deployment. 71 | 72 | -------------------------------------------------------------------------------- /tests/fixtures/whl_info/RECORD: -------------------------------------------------------------------------------- 1 | c7n_gcp/__init__.py,sha256=047jWuqQJ9y8E890HQc54IOBixoLTr-X2Y-HLjhF7zs,81 2 | c7n_gcp/actions/__init__.py,sha256=8i9zFfTLDhPS9XBKtMLnLhQ_TAzWXYUTANLiE148dvM,270 3 | c7n_gcp/actions/core.py,sha256=Zu9ExHtYo6MXDOZN24CrITlMKP9ieHjppocgdIQRE6k,3618 4 | c7n_gcp/actions/cscc.py,sha256=T1QGjl-kXbnDh47VzTtJpOTQ6WzS1BydjqoTbNBqEcY,8114 5 | c7n_gcp/actions/iampolicy.py,sha256=jQyrbp5GFajmrIGyo_z2Cl2HDm8BA92MJRxgzPEJhco,11155 6 | c7n_gcp/actions/labels.py,sha256=8x4usTCGaibC0dJWdbFvG3lfwGHzkTdyMrVK37St1H4,6591 7 | c7n_gcp/actions/notify.py,sha256=m4x1dZ8uzjLvRpz18pTWHa1Rha4U7MC5Ao4VQq60Sf4,3428 8 | c7n_gcp/client.py,sha256=C0QrLeXV1tCyxeprW29Wrm6JV7t9rwZYwUn1k74NW_s,17475 9 | c7n_gcp/entry.py,sha256=2OJvs82k_xRJts8pGsW5wsjMVguHnUXaRshrvBQ66J4,470 10 | c7n_gcp/filters/__init__.py,sha256=V0h2jol91abHsiJm07h598vrlzDRvQls8ocTIp2idbM,250 11 | c7n_gcp/filters/alerts.py,sha256=XDQgfs_U4F5S74ctyyLWd3Ma9_cosKT4AMBevcARHzY,2013 12 | c7n_gcp/filters/iampolicy.py,sha256=leBdiqEIUQb6mBTzduP5vSvRxTnf70WRbCuhcT9ndqI,5752 13 | c7n_gcp/filters/labels.py,sha256=oJvdFeJ8OoRRdPl1NmY4fO2eMSsrwA3QwNrUbF-ikk4,4146 14 | c7n_gcp/filters/metrics.py,sha256=g4WrnPwJUgPDoxRIu_K-bURHI5A-AoKoTmaIzKGlYgc,7483 15 | c7n_gcp/filters/sccfindings.py,sha256=4RY66uyVeXVsY3us2RrYm97666T8oWASctTV_BObUOY,3341 16 | c7n_gcp/handler.py,sha256=j8t8VzdqKnp_Q9g1PrzVzegCgUSYfddL-sl8O0bSrJE,1631 17 | c7n_gcp/mu.py,sha256=4gYqazxo5tHSDKQNGqWcwC6WyEPFyn5LnfrDcccRO8o,26883 18 | c7n_gcp/output.py,sha256=rKBWTmeNNpDeU7q_JLroazk50D7TFHVGG3AsNXNrCz0,6267 19 | c7n_gcp/policy.py,sha256=e7GaGFteX1HvQYfHiQ1ZwnlkJcLcUjg6cCAf8aVYv0E,10015 20 | c7n_gcp/provider.py,sha256=lbhj5lzAYiqfsjbsm68lDQeTcZHRxMdDiY6gZ2JmZgw,844 21 | c7n_gcp/query.py,sha256=qb9qtjsQ5DO_TmdA6IHpXndwTyGLBtfAoQqmJFDLgbY,18613 22 | c7n_gcp/resources/__init__.py,sha256=047jWuqQJ9y8E890HQc54IOBixoLTr-X2Y-HLjhF7zs,81 23 | c7n_gcp/resources/appengine.py,sha256=mxrd4sW5p3utpfNuAeWjEJJwnSrefSGRdDw6DyE-gIg,5551 24 | c7n_gcp/resources/bigquery.py,sha256=gQzi1KYyBjRiQYKQRA6Dc2SxfpKTOvX-O4RQ_1ud6oI,5238 25 | c7n_gcp/resources/build.py,sha256=pAH1ktjPBwKSPX8Zh7u-IUVaU8FvY7vyMIA95VTjeII,705 26 | c7n_gcp/resources/cloudbilling.py,sha256=LqaEkjR4v7UCBHqfhD_IG4SXoCWfRf0HPEZqDRnx8vU,1077 27 | c7n_gcp/resources/compute.py,sha256=Kibo5r1oBZTL9Y1UWLOK8XQWCLt7EjR_wzG9xjasuGQ,21286 28 | c7n_gcp/resources/dataflow.py,sha256=gjaEYIpiWHkRvOIA5S1WH0_o9Xokv-UVHpRnflNu0kQ,1983 29 | c7n_gcp/resources/deploymentmanager.py,sha256=uf183-m7k3jPDw3452MhkPc5IpnxSRahfiaYWxhb0XE,1923 30 | c7n_gcp/resources/dns.py,sha256=nOqxHM_LjTlnae26MMS3MbidkGtDH9wBHltEgkefqe4,2926 31 | c7n_gcp/resources/function.py,sha256=vx6QcIGArPIIkVGfXVZ2A8Qjvi9eZxi7tswqD7d1QSc,2034 32 | c7n_gcp/resources/gke.py,sha256=7kONwiKmSp-ml_H0PCZCqDbZctxORi6iUotsf_b4FF0,5230 33 | c7n_gcp/resources/iam.py,sha256=G-ty1_wKdZrR37WAC-IqqOkD7Eeek7i_W_LwOgXz2FQ,7327 34 | c7n_gcp/resources/kms.py,sha256=-pNLr6bkIuPb8OMzQbwJ6FtqchlzVwmziR10FMBUyxc,7113 35 | c7n_gcp/resources/loadbalancer.py,sha256=YZRUtgEs93URVNpqj7G5DgJ7Ffg5Mlo0lbW98a-6JJ4,19877 36 | c7n_gcp/resources/logging.py,sha256=6M40BDLJprQBwCA1waZc3eD6u_qt_h4d-STQDyYjfKE,3460 37 | c7n_gcp/resources/mlengine.py,sha256=stt4bu29LyjoJB3ja3nxkVteG58eUsIBcsq6PZyxbUU,2067 38 | c7n_gcp/resources/network.py,sha256=SZppu8bfrYKW93dV4c7zaukCgBT_u0WsZYBvQX1hrLg,12268 39 | c7n_gcp/resources/pubsub.py,sha256=tQfkjySFDJ_0nlIiIyFtSIr-FaeZILSedQqkoh7ZGE8,3151 40 | c7n_gcp/resources/resource_map.py,sha256=jxtE74HF_HDEVskO_ZpBfofSm-_UuG6PFlhC6I3zM34,5850 41 | c7n_gcp/resources/resourcemanager.py,sha256=2nKEhjE0nLNTZnkwJYrGJ74nU5Hao6WQNX-Hzjqiy8w,12428 42 | c7n_gcp/resources/service.py,sha256=BK6X6i_n8mMHEnVc9RMxG0r06Vs6m2XoEq4qKsMUrzU,2655 43 | c7n_gcp/resources/source.py,sha256=u5VHGKDtKV7gJo8pFDf-huH6htPVARCd42LEmiolTp0,882 44 | c7n_gcp/resources/spanner.py,sha256=TlaYcv0moS6riryAWTEE3ipvPXgYxgCBvyYW2hW8Cao,5989 45 | c7n_gcp/resources/sql.py,sha256=P_btXC8Umuhr4Cq8st8njIdZNOxX56REgigU73mar_I,8135 46 | c7n_gcp/resources/storage.py,sha256=Cxot04-gDrtgfyFL9h9IJsWDiEG8PEGZTNsS2rADAC0,2938 47 | c7n_gcp-0.4.22.dist-info/METADATA,sha256=0XCELuIa3wL7uz9DID_B_BIoHQ7_EPnhLVK1WC0COts,2312 48 | c7n_gcp-0.4.22.dist-info/WHEEL,sha256=kLuE8m1WYU0Ig0_YEGrXyTtiJvKPpLpDEiChiNyei5Y,88 49 | c7n_gcp-0.4.22.dist-info/RECORD,, 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Freeze Wheel Plugin 2 | 3 | Poetry plugin for creating frozen wheels using lockfiles. 4 | 5 | ## Why 6 | 7 | A common issue when publishing a Python application's release into PyPI, is whether or not the dependencies specified will continue to work over time. This tends to happen due to a confluence of reasons, poor dependency specification, bad observance of semantic versioning, or poor release management by the dependency. That translates to a reality where installing an older release of the application is unlikely to work, due to changes in the underlying dependency graph. 8 | 9 | The dependency ecosystem is both complex and fragile. The emergence of lock files to ensure repeatability is testimony both to the problem and one solution. Yet when we go to publish in the packaging ecosystem we do so with non frozen dependencies specifications not with lockfiles. That means the testing pipelines that goes to produce and validate a release is against a lockfile but the release artifact is divorced of the lockfile contents, and starts to diverge from the moment of publication. 10 | 11 | The various language package distribution channels (npm, pypi, rubygems, etc) are used for two different primary distribution purposes, for both libraries and applications. Generally speaking the extant behavior is reasonable for a library. Libraries should be relatively liberal on their own dependencies baring perhaps major versions to minimize conflicts for applications depending on them and ideally consist of minimal dependencies graphs. But for applications distribution, repeatable and verifyable installs are fundamental goals with potentially large dependency graphs. Using a frozen dependency graph versus version specifications is the only way to ensure repeatiblity of installation over time. Fundamentally the two different distribution purposes have different audiences, ie. libraries have developers and applications as consumers, applications have users as consumers. 12 | 13 | ## What 14 | 15 | A post build / pre publish command to allow for creating wheels with frozen dependencies. Basically we update wheel metadata for Requires-Dist to replace the pyproject.toml based version specification to a frozen (ie. ==version) one based on the version from the poetry lock information. 16 | 17 | 18 | Note we can't use poetry to publish because the frozen wheel because it uses metadata from pyproject.toml instead 19 | of frozen wheel metadata. 20 | 21 | ### Optional Dependencies 22 | 23 | Frozen wheel metadata will contain [Provides-Extra](https://packaging.python.org/en/latest/specifications/core-metadata/#provides-extra-multiple-use) entries for any [extra / optional dependencies](https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#dependencies-optional-dependencies). Frozen [Requires-Dist](https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata-requires-dist) lines will specify `extra` names _for packages that appear only in the optional/extra dependency graph. 24 | 25 | If a package appears as both a nested "main" dependency and also as an "extra" dependency, its `Requires-Dist` entry in the frozen wheel _will not_ specify an extra name. 26 | 27 | To define this behavior in relation to poetry's [export plugin](https://github.com/python-poetry/poetry-plugin-export/), these two flows should result in the same installed package set: 28 | 29 | ```console 30 | # Export Flow 31 | poetry export -f requirements.txt > requirements.txt && pip install -r requirements.txt 32 | 33 | # Freeze-wheel Flow 34 | poetry build && poetry freeze-wheel && pip install my_frozen_wheel 35 | ``` 36 | 37 | And introducing extras: 38 | 39 | ```console 40 | # Export Flow 41 | poetry export --extras gcp -f requirements.txt && pip install -r requirements.txt 42 | 43 | # Freeze-wheel Flow 44 | poetry build && poetry freeze-wheel && pip install my_frozen_wheel[gcp] 45 | ``` 46 | 47 | The difference is in when to choose which extras to install - `export` does that at freeze time. `freeze-wheel` embeds the extra _context_ at freeze time, but defers the actual extra selection until install time. 48 | 49 | ## Usage 50 | 51 | ```shell 52 | # install plugin 53 | poetry self add poetry-plugin-freeze 54 | 55 | # build per normal 56 | poetry build 57 | 58 | # add freeze step 59 | poetry freeze-wheel 60 | 61 | # avoid freezing specific packages 62 | poetry freeze-wheel --exclude boto3 -e attrs 63 | 64 | # Note we can't use poetry to publish because it uses metadata from pyproject.toml instead 65 | # of frozen wheel metadata. 66 | 67 | # publish per normal 68 | twine upload dist/*.whl 69 | ``` 70 | 71 | ## Mono-Repo Support 72 | 73 | To support mono repos consisting of multiple libraries/applications, when creating a frozen wheel, main group dependencies specified by path can be optionally substituted out for references to their release artifact versions. 74 | 75 | This assumes automation to run build and publish across the various subpackages, ie typically via make or just. 76 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_no_deps/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "app-b" 5 | version = "0.1" 6 | description = "lorem ipsum" 7 | optional = false 8 | python-versions = "^3.8" 9 | files = [] 10 | develop = true 11 | 12 | [package.dependencies] 13 | pytest = "^7.1" 14 | ruff = "^0.0.259" 15 | 16 | [package.source] 17 | type = "directory" 18 | url = "../.." 19 | 20 | [[package]] 21 | name = "colorama" 22 | version = "0.4.6" 23 | description = "Cross-platform colored terminal text." 24 | optional = false 25 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 26 | files = [ 27 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 28 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 29 | ] 30 | 31 | [[package]] 32 | name = "exceptiongroup" 33 | version = "1.2.1" 34 | description = "Backport of PEP 654 (exception groups)" 35 | optional = false 36 | python-versions = ">=3.7" 37 | files = [ 38 | {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, 39 | {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, 40 | ] 41 | 42 | [package.extras] 43 | test = ["pytest (>=6)"] 44 | 45 | [[package]] 46 | name = "iniconfig" 47 | version = "2.0.0" 48 | description = "brain-dead simple config-ini parsing" 49 | optional = false 50 | python-versions = ">=3.7" 51 | files = [ 52 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 53 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 54 | ] 55 | 56 | [[package]] 57 | name = "packaging" 58 | version = "24.0" 59 | description = "Core utilities for Python packages" 60 | optional = false 61 | python-versions = ">=3.7" 62 | files = [ 63 | {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, 64 | {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, 65 | ] 66 | 67 | [[package]] 68 | name = "pluggy" 69 | version = "1.5.0" 70 | description = "plugin and hook calling mechanisms for python" 71 | optional = false 72 | python-versions = ">=3.8" 73 | files = [ 74 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 75 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 76 | ] 77 | 78 | [package.extras] 79 | dev = ["pre-commit", "tox"] 80 | testing = ["pytest", "pytest-benchmark"] 81 | 82 | [[package]] 83 | name = "pytest" 84 | version = "7.4.4" 85 | description = "pytest: simple powerful testing with Python" 86 | optional = false 87 | python-versions = ">=3.7" 88 | files = [ 89 | {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, 90 | {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, 91 | ] 92 | 93 | [package.dependencies] 94 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 95 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 96 | iniconfig = "*" 97 | packaging = "*" 98 | pluggy = ">=0.12,<2.0" 99 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 100 | 101 | [package.extras] 102 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 103 | 104 | [[package]] 105 | name = "ruff" 106 | version = "0.0.259" 107 | description = "An extremely fast Python linter, written in Rust." 108 | optional = false 109 | python-versions = ">=3.7" 110 | files = [ 111 | {file = "ruff-0.0.259-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:f3938dc45e2a3f818e9cbd53007265c22246fbfded8837b2c563bf0ebde1a226"}, 112 | {file = "ruff-0.0.259-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:22e1e35bf5f12072cd644d22afd9203641ccf258bc14ff91aa1c43dc14f6047d"}, 113 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2fb20e89e85d147c85caa807707a1488bccc1f3854dc3d53533e89b52a0c5ff"}, 114 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49e903bcda19f6bb0725a962c058eb5d61f40d84ef52ed53b61939b69402ab4e"}, 115 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71f0ef1985e9a6696fa97da8459917fa34bdaa2c16bd33bd5edead585b7d44f7"}, 116 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7cfef26619cba184d59aa7fa17b48af5891d51fc0b755a9bc533478a10d4d066"}, 117 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79b02fa17ec1fd8d306ae302cb47fb614b71e1f539997858243769bcbe78c6d9"}, 118 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:428507fb321b386dda70d66cd1a8aa0abf51d7c197983d83bb9e4fa5ee60300b"}, 119 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5fbaea9167f1852757f02133e5daacdb8c75b3431343205395da5b10499927a"}, 120 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:40ae87f2638484b7e8a7567b04a7af719f1c484c5bf132038b702bb32e1f6577"}, 121 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:29e2b77b7d5da6a7dd5cf9b738b511355c5734ece56f78e500d4b5bffd58c1a0"}, 122 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b3c1beacf6037e7f0781d4699d9a2dd4ba2462f475be5b1f45cf84c4ba3c69d"}, 123 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:daaea322e7e85f4c13d82be9536309e1c4b8b9851bb0cbc7eeb15d490fd46bf9"}, 124 | {file = "ruff-0.0.259-py3-none-win32.whl", hash = "sha256:38704f151323aa5858370a2f792e122cc25e5d1aabe7d42ceeab83da18f0b456"}, 125 | {file = "ruff-0.0.259-py3-none-win_amd64.whl", hash = "sha256:aa9449b898287e621942cc71b9327eceb8f0c357e4065fecefb707ef2d978df8"}, 126 | {file = "ruff-0.0.259-py3-none-win_arm64.whl", hash = "sha256:e4f39e18702de69faaaee3969934b92d7467285627f99a5b6ecd55a7d9f5d086"}, 127 | {file = "ruff-0.0.259.tar.gz", hash = "sha256:8b56496063ab3bfdf72339a5fbebb8bd46e5c5fee25ef11a9f03b208fa0562ec"}, 128 | ] 129 | 130 | [[package]] 131 | name = "tomli" 132 | version = "2.0.1" 133 | description = "A lil' TOML parser" 134 | optional = false 135 | python-versions = ">=3.7" 136 | files = [ 137 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 138 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 139 | ] 140 | 141 | [metadata] 142 | lock-version = "2.0" 143 | python-versions = "^3.10" 144 | content-hash = "6a9e8bc95e28d396c057acade30e940daf39374a602b89eb628df738f70f12d7" 145 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "attrs" 5 | version = "22.2.0" 6 | description = "Classes Without Boilerplate" 7 | category = "main" 8 | optional = false 9 | python-versions = ">=3.6" 10 | files = [ 11 | {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, 12 | {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, 13 | ] 14 | 15 | [package.extras] 16 | cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] 17 | dev = ["attrs[docs,tests]"] 18 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] 19 | tests = ["attrs[tests-no-zope]", "zope.interface"] 20 | tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] 21 | 22 | [[package]] 23 | name = "colorama" 24 | version = "0.4.6" 25 | description = "Cross-platform colored terminal text." 26 | category = "main" 27 | optional = false 28 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 29 | files = [ 30 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 31 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 32 | ] 33 | 34 | [[package]] 35 | name = "exceptiongroup" 36 | version = "1.1.0" 37 | description = "Backport of PEP 654 (exception groups)" 38 | category = "main" 39 | optional = false 40 | python-versions = ">=3.7" 41 | files = [ 42 | {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, 43 | {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, 44 | ] 45 | 46 | [package.extras] 47 | test = ["pytest (>=6)"] 48 | 49 | [[package]] 50 | name = "iniconfig" 51 | version = "2.0.0" 52 | description = "brain-dead simple config-ini parsing" 53 | category = "main" 54 | optional = false 55 | python-versions = ">=3.7" 56 | files = [ 57 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 58 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 59 | ] 60 | 61 | [[package]] 62 | name = "packaging" 63 | version = "23.0" 64 | description = "Core utilities for Python packages" 65 | category = "main" 66 | optional = false 67 | python-versions = ">=3.7" 68 | files = [ 69 | {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, 70 | {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, 71 | ] 72 | 73 | [[package]] 74 | name = "pluggy" 75 | version = "1.0.0" 76 | description = "plugin and hook calling mechanisms for python" 77 | category = "main" 78 | optional = false 79 | python-versions = ">=3.6" 80 | files = [ 81 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 82 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 83 | ] 84 | 85 | [package.extras] 86 | dev = ["pre-commit", "tox"] 87 | testing = ["pytest", "pytest-benchmark"] 88 | 89 | [[package]] 90 | name = "pytest" 91 | version = "7.2.2" 92 | description = "pytest: simple powerful testing with Python" 93 | category = "main" 94 | optional = false 95 | python-versions = ">=3.7" 96 | files = [ 97 | {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, 98 | {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, 99 | ] 100 | 101 | [package.dependencies] 102 | attrs = ">=19.2.0" 103 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 104 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 105 | iniconfig = "*" 106 | packaging = "*" 107 | pluggy = ">=0.12,<2.0" 108 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 109 | 110 | [package.extras] 111 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 112 | 113 | [[package]] 114 | name = "ruff" 115 | version = "0.0.259" 116 | description = "An extremely fast Python linter, written in Rust." 117 | category = "main" 118 | optional = false 119 | python-versions = ">=3.7" 120 | files = [ 121 | {file = "ruff-0.0.259-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:f3938dc45e2a3f818e9cbd53007265c22246fbfded8837b2c563bf0ebde1a226"}, 122 | {file = "ruff-0.0.259-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:22e1e35bf5f12072cd644d22afd9203641ccf258bc14ff91aa1c43dc14f6047d"}, 123 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2fb20e89e85d147c85caa807707a1488bccc1f3854dc3d53533e89b52a0c5ff"}, 124 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49e903bcda19f6bb0725a962c058eb5d61f40d84ef52ed53b61939b69402ab4e"}, 125 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71f0ef1985e9a6696fa97da8459917fa34bdaa2c16bd33bd5edead585b7d44f7"}, 126 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7cfef26619cba184d59aa7fa17b48af5891d51fc0b755a9bc533478a10d4d066"}, 127 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79b02fa17ec1fd8d306ae302cb47fb614b71e1f539997858243769bcbe78c6d9"}, 128 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:428507fb321b386dda70d66cd1a8aa0abf51d7c197983d83bb9e4fa5ee60300b"}, 129 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5fbaea9167f1852757f02133e5daacdb8c75b3431343205395da5b10499927a"}, 130 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:40ae87f2638484b7e8a7567b04a7af719f1c484c5bf132038b702bb32e1f6577"}, 131 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:29e2b77b7d5da6a7dd5cf9b738b511355c5734ece56f78e500d4b5bffd58c1a0"}, 132 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b3c1beacf6037e7f0781d4699d9a2dd4ba2462f475be5b1f45cf84c4ba3c69d"}, 133 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:daaea322e7e85f4c13d82be9536309e1c4b8b9851bb0cbc7eeb15d490fd46bf9"}, 134 | {file = "ruff-0.0.259-py3-none-win32.whl", hash = "sha256:38704f151323aa5858370a2f792e122cc25e5d1aabe7d42ceeab83da18f0b456"}, 135 | {file = "ruff-0.0.259-py3-none-win_amd64.whl", hash = "sha256:aa9449b898287e621942cc71b9327eceb8f0c357e4065fecefb707ef2d978df8"}, 136 | {file = "ruff-0.0.259-py3-none-win_arm64.whl", hash = "sha256:e4f39e18702de69faaaee3969934b92d7467285627f99a5b6ecd55a7d9f5d086"}, 137 | {file = "ruff-0.0.259.tar.gz", hash = "sha256:8b56496063ab3bfdf72339a5fbebb8bd46e5c5fee25ef11a9f03b208fa0562ec"}, 138 | ] 139 | 140 | [[package]] 141 | name = "tomli" 142 | version = "2.0.1" 143 | description = "A lil' TOML parser" 144 | category = "main" 145 | optional = false 146 | python-versions = ">=3.7" 147 | files = [ 148 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 149 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 150 | ] 151 | 152 | [metadata] 153 | lock-version = "2.0" 154 | python-versions = "^3.8" 155 | content-hash = "c34c4c3977de75f46645facc3a58a177de8c4f7759f1254c02c43904771a9c32" 156 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2025 cloud-custodian 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /tests/test_freeze.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import re 3 | import zipfile 4 | from email.parser import Parser 5 | from io import StringIO 6 | 7 | from cleo.io.null_io import NullIO 8 | from cleo.testers.command_tester import CommandTester 9 | from poetry.console.application import Application 10 | from poetry.factory import Factory 11 | from poetry_plugin_freeze.app import IcedPoet, get_sha256_digest, project_roots 12 | 13 | 14 | def test_project_roots(fixture_root): 15 | assert sorted(project_roots(fixture_root)) == [ 16 | fixture_root / "nested_packages", 17 | fixture_root / "nested_packages" / "others" / "app_c", 18 | fixture_root / "nested_packages" / "others" / "app_no_deps", 19 | fixture_root / "nested_packages" / "others" / "app_with_extras", 20 | fixture_root / "non_poetry_package", 21 | ] 22 | 23 | 24 | def test_excluded_config_path_project_roots(fixture_root): 25 | assert sorted(project_roots(fixture_root, fixture_root / "nested_packages" / "others")) == [ 26 | fixture_root / "nested_packages", 27 | fixture_root / "non_poetry_package", 28 | ] 29 | 30 | 31 | def parse_md(md_text: bytes): 32 | return Parser().parsestr(md_text.decode("utf8")) 33 | 34 | 35 | def parse_record(record_text: bytes): 36 | return list(csv.reader(StringIO(record_text.decode("utf8")))) 37 | 38 | 39 | def test_freeze_command_options(fixture_root, monkeypatch): 40 | poet_options = {} 41 | 42 | def mock_check(self): 43 | poet_options["wheel_dir"] = self.wheel_dir 44 | poet_options["exclude_packages"] = self.exclude_packages 45 | return True 46 | 47 | def mock_freeze(self): 48 | return [] 49 | 50 | monkeypatch.setattr(IcedPoet, "check", mock_check) 51 | monkeypatch.setattr(IcedPoet, "freeze", mock_freeze) 52 | 53 | poetry = Factory().create_poetry(fixture_root) 54 | app = Application() 55 | app._poetry = poetry 56 | app._load_plugins(NullIO()) 57 | 58 | cmd = app.find("freeze-wheel") 59 | tester = CommandTester(cmd) 60 | 61 | tester.execute("--exclude boto3 -e attrs --wheel-dir mydir") 62 | assert poet_options["wheel_dir"] == "mydir" 63 | assert poet_options["exclude_packages"] == ["boto3", "attrs"] 64 | 65 | tester.execute() 66 | assert poet_options["wheel_dir"] == "dist" 67 | assert poet_options["exclude_packages"] == [] 68 | 69 | assert re.match("skipping.*non_poetry_package", cmd.io.fetch_error()) 70 | 71 | 72 | def test_freeze_nested(fixture_root, fixture_copy): 73 | package = fixture_copy(fixture_root / "nested_packages") 74 | sub_package = fixture_copy(fixture_root / "nested_packages" / "others" / "app_c") 75 | 76 | iced_pkg = IcedPoet(package) 77 | iced_sub = IcedPoet(sub_package) 78 | fridge = {iced_pkg.name: iced_pkg, iced_sub.name: iced_sub} 79 | iced_sub.set_fridge(fridge) 80 | 81 | wheels = iced_sub.freeze() 82 | assert len(wheels) == 1 83 | 84 | wheel = zipfile.ZipFile(wheels[0]) 85 | 86 | records = parse_record( 87 | wheel.open(f"{iced_sub.distro_name}-{iced_sub.version}.dist-info/RECORD").read() 88 | ) 89 | md = parse_md( 90 | wheel.open(f"{iced_sub.distro_name}-{iced_sub.version}.dist-info/METADATA").read() 91 | ) 92 | 93 | expected_headers = [ 94 | ("Metadata-Version", "2.3"), 95 | ("Name", "app_c"), 96 | ("Version", "0.2"), 97 | ("Summary", "lorem ipsum"), 98 | ("License", "Apache-2.0"), 99 | ("Author", "SideCars"), 100 | ("Requires-Python", ">=3.10,<4.0"), 101 | ("Classifier", "License :: OSI Approved :: Apache Software License"), 102 | ("Classifier", "Programming Language :: Python :: 3"), 103 | ("Classifier", "Programming Language :: Python :: 3.10"), 104 | ("Classifier", "Programming Language :: Python :: 3.11"), 105 | ("Classifier", "Programming Language :: Python :: 3.12"), 106 | ("Classifier", "Programming Language :: Python :: 3.13"), 107 | ( 108 | "Requires-Dist", 109 | 'pytest (==7.2.2) ; python_version >= "3.10" and python_version < "4.0"', 110 | ), 111 | ( 112 | "Requires-Dist", 113 | 'attrs (==22.2.0) ; python_version >= "3.10" and python_version < "4.0"', 114 | ), 115 | ( 116 | "Requires-Dist", 117 | 'colorama (==0.4.6) ; python_version >= "3.10" and python_version < "4.0" ' 118 | 'and sys_platform == "win32"', 119 | ), 120 | ( 121 | "Requires-Dist", 122 | 'exceptiongroup (==1.1.0) ; python_version >= "3.10" and python_version < "3.11"', 123 | ), 124 | ( 125 | "Requires-Dist", 126 | 'iniconfig (==2.0.0) ; python_version >= "3.10" and python_version < "4.0"', 127 | ), 128 | ( 129 | "Requires-Dist", 130 | 'packaging (==23.0) ; python_version >= "3.10" and python_version < "4.0"', 131 | ), 132 | ( 133 | "Requires-Dist", 134 | 'pluggy (==1.0.0) ; python_version >= "3.10" and python_version < "4.0"', 135 | ), 136 | ( 137 | "Requires-Dist", 138 | 'tomli (==2.0.1) ; python_version >= "3.10" and python_full_version <= ' '"3.11.0a6"', 139 | ), 140 | ( 141 | "Requires-Dist", 142 | 'urllib3 (==1.26.20) ; python_version >= "3.10" and python_version < "3.11"', 143 | ), 144 | ( 145 | "Requires-Dist", 146 | 'urllib3 (==2.3.0) ; python_version >= "3.11" and python_version < "4.0"', 147 | ), 148 | ( 149 | "Requires-Dist", 150 | 'pytest-cov (==4.0.0) ; python_version >= "3.10" and python_version < "4.0"', 151 | ), 152 | ( 153 | "Requires-Dist", 154 | 'coverage (==7.2.1) ; python_version >= "3.10" and python_version < "4.0"', 155 | ), 156 | ] 157 | assert sorted(md._headers) == sorted(expected_headers) 158 | 159 | assert records == [ 160 | [ 161 | "app_c/__init__.py", 162 | "sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", 163 | "0", 164 | ], 165 | [ 166 | "app_c-0.2.dist-info/WHEEL", 167 | "sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM", 168 | "88", 169 | ], 170 | ["app_c-0.2.dist-info/RECORD", "", ""], 171 | [ 172 | "app_c-0.2.dist-info/METADATA", 173 | "sha256=Y0clowJC222mCfG3SHng-M--m9e9Xz9CHRVarGNDlcY", 174 | "1547", 175 | ], 176 | ] 177 | 178 | md_bytes = wheel.open(f"{iced_sub.distro_name}-{iced_sub.version}.dist-info/METADATA").read() 179 | assert len(md_bytes) == 1547 180 | assert get_sha256_digest(md_bytes) == "Y0clowJC222mCfG3SHng-M--m9e9Xz9CHRVarGNDlcY" 181 | 182 | 183 | def test_freeze_no_deps(fixture_root, fixture_copy): 184 | package = fixture_copy(fixture_root / "nested_packages") 185 | sub_package = fixture_copy(fixture_root / "nested_packages" / "others" / "app_no_deps") 186 | 187 | iced_pkg = IcedPoet(package) 188 | iced_sub = IcedPoet(sub_package) 189 | fridge = {iced_pkg.name: iced_pkg, iced_sub.name: iced_sub} 190 | iced_sub.set_fridge(fridge) 191 | 192 | wheels = iced_sub.freeze() 193 | assert len(wheels) == 1 194 | 195 | wheel = zipfile.ZipFile(wheels[0]) 196 | 197 | records = parse_record( 198 | wheel.open(f"{iced_sub.distro_name}-{iced_sub.version}.dist-info/RECORD").read() 199 | ) 200 | md = parse_md( 201 | wheel.open(f"{iced_sub.distro_name}-{iced_sub.version}.dist-info/METADATA").read() 202 | ) 203 | 204 | expected_headers = [ 205 | ("Metadata-Version", "2.1"), 206 | ("Name", "app_no_deps"), 207 | ("Version", "0.2"), 208 | ("Summary", "lorem ipsum"), 209 | ("License", "Apache-2.0"), 210 | ("Author", "SideCars"), 211 | ("Requires-Python", ">=3.10,<4.0"), 212 | ("Classifier", "License :: OSI Approved :: Apache Software License"), 213 | ("Classifier", "Programming Language :: Python :: 3"), 214 | ("Classifier", "Programming Language :: Python :: 3.10"), 215 | ("Classifier", "Programming Language :: Python :: 3.11"), 216 | ("Classifier", "Programming Language :: Python :: 3.12"), 217 | ] 218 | assert sorted(md._headers) == sorted(expected_headers) 219 | 220 | assert records == [ 221 | [ 222 | "app_no_deps/__init__.py", 223 | "sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU", 224 | "0", 225 | ], 226 | [ 227 | "app_no_deps-0.2.dist-info/WHEEL", 228 | "sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg", 229 | "88", 230 | ], 231 | ["app_no_deps-0.2.dist-info/RECORD", "", ""], 232 | [ 233 | "app_no_deps-0.2.dist-info/METADATA", 234 | "sha256=VX5SQgKuWZ04guxl1ZXZAOsK49nxKioLba_W4k0SzXE", 235 | "405", 236 | ], 237 | ] 238 | 239 | md_bytes = wheel.open(f"{iced_sub.distro_name}-{iced_sub.version}.dist-info/METADATA").read() 240 | assert len(md_bytes) == 405 241 | assert get_sha256_digest(md_bytes) == "VX5SQgKuWZ04guxl1ZXZAOsK49nxKioLba_W4k0SzXE" 242 | 243 | 244 | def test_freeze_extras(fixture_root, fixture_copy): 245 | nested_packages = fixture_copy(fixture_root / "nested_packages") 246 | 247 | iced_pkg = IcedPoet(nested_packages / "others" / "app_with_extras") 248 | iced_pkg.set_fridge({iced_pkg.name: iced_pkg}) 249 | wheels = iced_pkg.freeze() 250 | assert len(wheels) == 1 251 | 252 | wheel = zipfile.ZipFile(wheels[0]) 253 | 254 | md = parse_md( 255 | wheel.open(f"{iced_pkg.distro_name}-{iced_pkg.version}.dist-info/METADATA").read() 256 | ) 257 | 258 | md_requirements = {} 259 | for header_type, header_value in md._headers: 260 | if header_type != "Requires-Dist": 261 | continue 262 | pkg_name, requirements = header_value.split(maxsplit=1) 263 | md_requirements[pkg_name] = requirements 264 | 265 | # app-c is installed as part of the "bells" extra 266 | assert 'extra == "bells"' in md_requirements["app-c"] 267 | 268 | # ruff shows up in both the base dependency tree 269 | # and as part of extras. Its inclusion in the base 270 | # set of dependencies should prevent it from carrying 271 | # an extra marker. 272 | assert "extra" not in md_requirements["ruff"] 273 | 274 | # tomli is an optional/extra dependency of coverage, 275 | # which can be pulled in by one or more top-level extra selections. 276 | # The frozen requirement should only include markers for 277 | # extras defined in the root package. 278 | assert all( 279 | [ 280 | 'extra == "bells"' in md_requirements["tomli"], 281 | 'extra == "whistles"' in md_requirements["tomli"], 282 | 'extra == "toml"' not in md_requirements["tomli"], 283 | ] 284 | ) 285 | 286 | 287 | def test_freeze_exclude_packages(fixture_root, fixture_copy): 288 | package = fixture_copy(fixture_root / "nested_packages") 289 | 290 | iced_pkg = IcedPoet(package, exclude_packages=["pytest", "ruff"]) 291 | iced_pkg.set_fridge({iced_pkg.name: iced_pkg}) 292 | wheels = iced_pkg.freeze() 293 | assert len(wheels) == 1 294 | 295 | wheel = zipfile.ZipFile(wheels[0]) 296 | 297 | md = parse_md( 298 | wheel.open(f"{iced_pkg.distro_name}-{iced_pkg.version}.dist-info/METADATA").read() 299 | ) 300 | 301 | md_requirements = {} 302 | for header_type, header_value in md._headers: 303 | if header_type != "Requires-Dist": 304 | continue 305 | pkg_name, requirements = header_value.split(maxsplit=1) 306 | md_requirements[pkg_name] = requirements 307 | 308 | for package, expected_version_constraint in [ 309 | # Excluded packages should not have frozen versions 310 | ("pytest", "(>=7.1,<8.0)"), 311 | ("ruff", "(>=0.0.259,<0.0.260)"), 312 | # ...but other packages should 313 | ("attrs", "(==22.2.0)"), 314 | ("colorama", "(==0.4.6)"), 315 | ("exceptiongroup", "(==1.1.0)"), 316 | ("iniconfig", "(==2.0.0)"), 317 | ("packaging", "(==23.0)"), 318 | ("pluggy", "(==1.0.0)"), 319 | ("tomli", "(==2.0.1)"), 320 | ]: 321 | assert expected_version_constraint in md_requirements[package] 322 | -------------------------------------------------------------------------------- /src/poetry_plugin_freeze/app.py: -------------------------------------------------------------------------------- 1 | from base64 import urlsafe_b64encode 2 | import csv 3 | from email.parser import Parser 4 | from functools import lru_cache 5 | from itertools import chain 6 | import hashlib 7 | from io import StringIO, TextIOWrapper 8 | from pathlib import Path 9 | import shutil 10 | import os 11 | import tempfile 12 | import zipfile 13 | 14 | from cleo.helpers import option 15 | from poetry.console.commands.command import Command 16 | from poetry.core.packages.dependency_group import MAIN_GROUP 17 | from poetry.core.version.markers import MultiMarker, SingleMarker 18 | from poetry.packages import DependencyPackage 19 | from poetry.utils.env import EnvManager 20 | from poetry.core.masonry.metadata import Metadata 21 | from poetry.core.masonry.utils.helpers import distribution_name 22 | from poetry.core.version.markers import union as marker_union 23 | from poetry.factory import Factory 24 | from poetry.plugins.application_plugin import ApplicationPlugin 25 | from poetry.core.packages.utils.utils import create_nested_marker 26 | from poetry.core.version.markers import parse_marker, BaseMarker 27 | from poetry.core.constraints.version import VersionConstraint 28 | 29 | from poetry_plugin_export.walker import get_project_dependency_packages, walk_dependencies 30 | 31 | try: 32 | from poetry.core.pyproject.exceptions import PyProjectError 33 | except ImportError: 34 | # Account for the PyProjectException --> PyProjectError rename in Poetry 2.0 35 | from poetry.core.pyproject.exceptions import PyProjectException as PyProjectError 36 | 37 | 38 | class FreezeCommand(Command): 39 | name = "freeze-wheel" 40 | 41 | options = [ 42 | option("wheel-dir", None, "Sub-directory containing wheels", default="dist", flag=False), 43 | option( 44 | "exclude", 45 | short_name="-e", 46 | description="A package name to exclude from freezing", 47 | flag=False, 48 | value_required=False, 49 | multiple=True, 50 | ), 51 | ] 52 | 53 | def project_roots(self, root): 54 | env = EnvManager(self.poetry).get() 55 | 56 | excludes = [] 57 | if env.is_venv(): 58 | excludes.append(env.path) 59 | 60 | return project_roots(root, *excludes) 61 | 62 | def handle(self) -> int: 63 | self.line("freezing wheels") 64 | root_dir = self._io and self._io.input.option("directory") or Path.cwd() 65 | 66 | fridge = {} 67 | for project_root in self.project_roots(root_dir): 68 | try: 69 | iced = IcedPoet(project_root, self.option("wheel-dir"), self.option("exclude")) 70 | iced.check() 71 | fridge[iced.name] = iced 72 | except (PyProjectError, RuntimeError) as err: 73 | self.line_error(f"skipping {project_root}: {err}") 74 | 75 | for iced in fridge.values(): 76 | iced.set_fridge(fridge) 77 | for w in iced.freeze(): 78 | self.line(f"froze {iced.name} {iced.version} -> {w}") 79 | 80 | return 0 81 | 82 | 83 | def factory(): 84 | return FreezeCommand() 85 | 86 | 87 | def get_python_marker_from_constraint(constraint: VersionConstraint) -> BaseMarker: 88 | return parse_marker(create_nested_marker("python_version", constraint)) 89 | 90 | 91 | class FreezeApplicationPlugin(ApplicationPlugin): 92 | def activate(self, application): 93 | application.command_loader.register_factory("freeze-wheel", factory) 94 | 95 | 96 | def config_path_excluded(config_path, *excludes): 97 | for exclude in excludes: 98 | if str(config_path).startswith(str(exclude)): 99 | return True 100 | return False 101 | 102 | 103 | def project_roots(root, *excludes): 104 | for config_path in Path(root).rglob("pyproject.toml"): 105 | if not config_path_excluded(config_path, *excludes): 106 | yield config_path.parent 107 | 108 | 109 | def get_sha256_digest(content: bytes): 110 | hashsum = hashlib.sha256() 111 | hashsum.update(content) 112 | hash_digest = urlsafe_b64encode(hashsum.digest()).decode("ascii").rstrip("=") 113 | return hash_digest 114 | 115 | 116 | class IcedPoet: 117 | factory = Factory() 118 | 119 | def __init__(self, project_dir, wheel_dir="dist", exclude_packages=()): 120 | self.project_dir = project_dir 121 | self.wheel_dir = wheel_dir 122 | self.poetry = self.factory.create_poetry(project_dir) 123 | self.meta = Metadata.from_package(self.poetry.package) 124 | self.fridge = None 125 | self.exclude_packages = exclude_packages 126 | 127 | def set_fridge(self, fridge): 128 | self.fridge = fridge 129 | 130 | def check(self): 131 | assert self.poetry.locker.is_locked() is True 132 | assert self.poetry.locker.is_fresh() is True 133 | 134 | @property 135 | def name(self): 136 | return self.poetry.package.name 137 | 138 | @property 139 | def distro_name(self): 140 | return distribution_name(self.name) 141 | 142 | @property 143 | def version(self): 144 | return self.poetry.package.version 145 | 146 | def get_wheels(self): 147 | dist_dir = self.project_dir / self.wheel_dir 148 | wheels = list(dist_dir.glob("*whl")) 149 | prefix = "%s-%s" % (self.distro_name, self.meta.version) 150 | for w in wheels: 151 | if not w.name.startswith(prefix): 152 | continue 153 | yield w 154 | 155 | def freeze(self): 156 | wheels = list(self.get_wheels()) 157 | if not wheels: 158 | return [] 159 | dep_packages = self.get_dep_packages() 160 | for w in wheels: 161 | self.freeze_wheel(w, dep_packages) 162 | return wheels 163 | 164 | def get_dep_packages(self): 165 | root_package = self.poetry.package.with_dependency_groups([MAIN_GROUP], only=True) 166 | 167 | return list( 168 | get_project_dependency_packages( 169 | self.poetry.locker, 170 | project_requires=root_package.all_requires, 171 | root_package_name=root_package.name, 172 | project_python_marker=get_python_marker_from_constraint( 173 | root_package.python_constraint 174 | ), 175 | extras=root_package.extras, 176 | ) 177 | ) 178 | 179 | @lru_cache(maxsize=None) 180 | def get_dependency_sources(self): 181 | """Determine the root source of each locked dependency 182 | 183 | For each locked dependency, determine whether it came 184 | as a base requirement or part of one or more extras. 185 | """ 186 | 187 | def _with_python_marker(requirements, root_package): 188 | """Augment requirements with the root package's python marker""" 189 | marked_requirements = [] 190 | for require in requirements: 191 | require = require.clone() 192 | require.marker = require.marker.intersect( 193 | get_python_marker_from_constraint(root_package.python_constraint), 194 | ) 195 | marked_requirements.append(require) 196 | return marked_requirements 197 | 198 | repository = self.poetry.locker.locked_repository() 199 | root_package = self.poetry.package 200 | locked_packages_by_name = {} 201 | for pkg in repository.packages: 202 | locked_packages_by_name.setdefault(pkg.name, []).append(pkg) 203 | dependency_sources = {} 204 | base_requires = [ 205 | dep 206 | for dep in root_package.requires 207 | if not dep.is_optional() or set(dep.in_extras) <= root_package.features 208 | ] 209 | 210 | # Identify nested dependencies that don't require extra selections. 211 | base_nested_dependencies = walk_dependencies( 212 | _with_python_marker(base_requires, root_package), 213 | packages_by_name=locked_packages_by_name, 214 | root_package_name=root_package.name, 215 | ) 216 | for d in base_nested_dependencies: 217 | dependency_sources.setdefault(d.name, set()).add("base") 218 | 219 | # Identify nested dependencies that come from one or more extra 220 | # selections. 221 | for extra in root_package.extras: 222 | extra_nested_dependencies = walk_dependencies( 223 | _with_python_marker(root_package.extras[extra], root_package), 224 | packages_by_name=locked_packages_by_name, 225 | root_package_name=root_package.name, 226 | ) 227 | for d in extra_nested_dependencies: 228 | dependency_sources.setdefault(d.name, set()).add(extra) 229 | return dependency_sources 230 | 231 | def compact_markers(self, dependency): 232 | """Update a dependency to consolidate its markers. 233 | 234 | This avoids duplication when there are multiple markers 235 | (for sets of python versions, for example). It also records 236 | extra dependency markers which are lost in the conversion 237 | from installed package to dependency. 238 | """ 239 | dep_sources = self.get_dependency_sources().get(dependency.name, set()) 240 | 241 | # Record extra markers only if a dependency is not included 242 | # in the base requirement set. 243 | new_marker = dependency.marker.without_extras() 244 | in_base = "base" in dep_sources 245 | in_extras = dep_sources - {"base"} 246 | if in_extras and not in_base: 247 | extra_markers = marker_union(*(SingleMarker("extra", extra) for extra in in_extras)) 248 | new_marker = MultiMarker(new_marker, extra_markers) 249 | dependency.marker = new_marker 250 | 251 | def get_frozen_deps(self, dep_packages, exclude_packages=None): 252 | lines = [] 253 | dependency_sources = self.get_dependency_sources() 254 | for dep_package in dep_packages: 255 | self.compact_markers(dep_package.dependency) 256 | # Freeze extra markers for dependencies which were pulled in via extras 257 | # Don't freeze markers if a dependency is also part of the base 258 | # dependency tree. 259 | freeze_extras = "base" not in dependency_sources.get(dep_package.dependency.name, set()) 260 | requirement = dep_package.dependency.to_pep_508(with_extras=freeze_extras) 261 | 262 | if dep_package.package.name in exclude_packages: 263 | lines.append(requirement) 264 | continue 265 | 266 | require_dist = "%s (==%s)" % (dep_package.package.name, dep_package.package.version) 267 | if ";" in requirement: 268 | markers = requirement.split(";", 1)[1].strip() 269 | require_dist += f" ; {markers}" 270 | lines.append(require_dist) 271 | return lines 272 | 273 | def replace_deps(self, dist_meta, dep_lines): 274 | start_pos = 0 275 | 276 | for m in dist_meta.get_all("Requires-Dist", ()): 277 | if not start_pos: 278 | start_pos = dist_meta._headers.index(("Requires-Dist", m)) 279 | dist_meta._headers.remove(("Requires-Dist", m)) 280 | 281 | for idx, h in enumerate(dep_lines): 282 | dist_meta._headers.insert(start_pos + idx, ("Requires-Dist", h)) 283 | 284 | return dist_meta 285 | 286 | def get_path_deps(self, group="dev"): 287 | # assuming we're consistent install across deps. 288 | group = self.poetry.package.dependency_group(group) 289 | for dep in group.dependencies: 290 | if not (dep.is_file() or dep.is_directory()): 291 | continue 292 | if dep.is_vcs() or dep.is_url(): 293 | continue 294 | iced = IcedPoet(dep.full_path) 295 | # Carry markers from the root package dependency through to the iced package 296 | self.compact_markers(dep) 297 | iced_dep = iced.poetry.package.to_dependency() 298 | iced_dep.marker = MultiMarker(dep.marker, iced_dep.marker) 299 | package_dep = DependencyPackage(dependency=iced_dep, package=iced.poetry.package) 300 | yield package_dep 301 | 302 | def freeze_record(self, records_fh, dist_meta, md_path): 303 | hash_digest = get_sha256_digest(str(dist_meta).encode("utf8")) 304 | output = StringIO() 305 | csv_params = { 306 | "delimiter": csv.excel.delimiter, 307 | "quotechar": csv.excel.quotechar, 308 | "lineterminator": "\n", 309 | } 310 | writer = csv.writer(output, **csv_params) 311 | reader = csv.reader(TextIOWrapper(records_fh, encoding="utf8"), **csv_params) 312 | 313 | for row in reader: 314 | if row[0] == md_path: 315 | continue 316 | writer.writerow(row) 317 | 318 | writer.writerow((md_path, f"sha256={hash_digest}", len(str(dist_meta).encode("utf8")))) 319 | return output.getvalue() 320 | 321 | def freeze_wheel(self, wheel_path, dep_packages): 322 | dist_info = "%s-%s.dist-info" % ( 323 | self.distro_name, 324 | self.meta.version, 325 | ) 326 | md_path = f"{dist_info}/METADATA" 327 | record_path = f"{dist_info}/RECORD" 328 | 329 | with zipfile.ZipFile(wheel_path) as source_whl: 330 | # freeze deps in metadata and update records 331 | md_text = source_whl.open(md_path).read().decode("utf8") 332 | dist_meta = Parser().parsestr(md_text) 333 | path_deps = self.get_path_deps(MAIN_GROUP) 334 | dep_lines = self.get_frozen_deps(chain(path_deps, dep_packages), self.exclude_packages) 335 | if dep_lines: 336 | self.replace_deps(dist_meta, dep_lines) 337 | 338 | with source_whl.open(record_path) as record_fh: 339 | record_text = self.freeze_record(record_fh, dist_meta, md_path) 340 | 341 | (fd, temp_path) = tempfile.mkstemp(suffix=".whl") 342 | with ( 343 | os.fdopen(fd, "w+b") as fd_file, 344 | zipfile.ZipFile(fd_file, mode="w", compression=zipfile.ZIP_DEFLATED) as frozen_whl, 345 | ): 346 | # first copy all files to frozen zip 347 | for info in source_whl.infolist(): 348 | if info.filename in (md_path, record_path): 349 | sample = info 350 | continue 351 | info_fh = source_whl.open(info) 352 | frozen_whl.writestr(info, info_fh.read(), compress_type=zipfile.ZIP_DEFLATED) 353 | 354 | # finally add in our modified files 355 | date_time = (2016, 1, 1, 0, 0, 0) 356 | 357 | md_info = zipfile.ZipInfo(md_path, date_time) 358 | md_info.external_attr = sample.external_attr 359 | frozen_whl.writestr( 360 | md_info, 361 | str(dist_meta).encode("utf8"), 362 | compress_type=zipfile.ZIP_DEFLATED, 363 | ) 364 | 365 | record_info = zipfile.ZipInfo(record_path, date_time) 366 | record_info.external_attr = sample.external_attr 367 | frozen_whl.writestr(record_path, record_text, compress_type=zipfile.ZIP_DEFLATED) 368 | 369 | shutil.move(temp_path, str(wheel_path)) 370 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_with_extras/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "app-b" 5 | version = "0.1" 6 | description = "lorem ipsum" 7 | category = "main" 8 | optional = true 9 | python-versions = "^3.8" 10 | files = [] 11 | develop = false 12 | 13 | [package.dependencies] 14 | pytest = "^7.1" 15 | ruff = "^0.0.259" 16 | 17 | [package.source] 18 | type = "directory" 19 | url = "../.." 20 | 21 | [[package]] 22 | name = "app-c" 23 | version = "0.2" 24 | description = "lorem ipsum" 25 | category = "main" 26 | optional = true 27 | python-versions = "^3.10" 28 | files = [] 29 | develop = true 30 | 31 | [package.dependencies] 32 | pytest = "^7.1" 33 | pytest-cov = "^4.0" 34 | 35 | [package.source] 36 | type = "directory" 37 | url = "../app_c" 38 | 39 | [[package]] 40 | name = "colorama" 41 | version = "0.4.6" 42 | description = "Cross-platform colored terminal text." 43 | category = "main" 44 | optional = true 45 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 46 | files = [ 47 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 48 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 49 | ] 50 | 51 | [[package]] 52 | name = "coverage" 53 | version = "7.2.3" 54 | description = "Code coverage measurement for Python" 55 | category = "main" 56 | optional = true 57 | python-versions = ">=3.7" 58 | files = [ 59 | {file = "coverage-7.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5"}, 60 | {file = "coverage-7.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4"}, 61 | {file = "coverage-7.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2"}, 62 | {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013"}, 63 | {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa"}, 64 | {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b"}, 65 | {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257"}, 66 | {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535"}, 67 | {file = "coverage-7.2.3-cp310-cp310-win32.whl", hash = "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91"}, 68 | {file = "coverage-7.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e"}, 69 | {file = "coverage-7.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79"}, 70 | {file = "coverage-7.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc"}, 71 | {file = "coverage-7.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df"}, 72 | {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623"}, 73 | {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d"}, 74 | {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93"}, 75 | {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"}, 76 | {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4"}, 77 | {file = "coverage-7.2.3-cp311-cp311-win32.whl", hash = "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925"}, 78 | {file = "coverage-7.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910"}, 79 | {file = "coverage-7.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c"}, 80 | {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9"}, 81 | {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1"}, 82 | {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1"}, 83 | {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21"}, 84 | {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841"}, 85 | {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48"}, 86 | {file = "coverage-7.2.3-cp37-cp37m-win32.whl", hash = "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1"}, 87 | {file = "coverage-7.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f"}, 88 | {file = "coverage-7.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab"}, 89 | {file = "coverage-7.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040"}, 90 | {file = "coverage-7.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1"}, 91 | {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911"}, 92 | {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462"}, 93 | {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c"}, 94 | {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd"}, 95 | {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152"}, 96 | {file = "coverage-7.2.3-cp38-cp38-win32.whl", hash = "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22"}, 97 | {file = "coverage-7.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367"}, 98 | {file = "coverage-7.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235"}, 99 | {file = "coverage-7.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21"}, 100 | {file = "coverage-7.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934"}, 101 | {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859"}, 102 | {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9"}, 103 | {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539"}, 104 | {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe"}, 105 | {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4"}, 106 | {file = "coverage-7.2.3-cp39-cp39-win32.whl", hash = "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30"}, 107 | {file = "coverage-7.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a"}, 108 | {file = "coverage-7.2.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0"}, 109 | {file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"}, 110 | ] 111 | 112 | [package.dependencies] 113 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 114 | 115 | [package.extras] 116 | toml = ["tomli"] 117 | 118 | [[package]] 119 | name = "exceptiongroup" 120 | version = "1.1.1" 121 | description = "Backport of PEP 654 (exception groups)" 122 | category = "main" 123 | optional = true 124 | python-versions = ">=3.7" 125 | files = [ 126 | {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, 127 | {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, 128 | ] 129 | 130 | [package.extras] 131 | test = ["pytest (>=6)"] 132 | 133 | [[package]] 134 | name = "iniconfig" 135 | version = "2.0.0" 136 | description = "brain-dead simple config-ini parsing" 137 | category = "main" 138 | optional = true 139 | python-versions = ">=3.7" 140 | files = [ 141 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 142 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 143 | ] 144 | 145 | [[package]] 146 | name = "packaging" 147 | version = "23.1" 148 | description = "Core utilities for Python packages" 149 | category = "main" 150 | optional = true 151 | python-versions = ">=3.7" 152 | files = [ 153 | {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, 154 | {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, 155 | ] 156 | 157 | [[package]] 158 | name = "pluggy" 159 | version = "1.0.0" 160 | description = "plugin and hook calling mechanisms for python" 161 | category = "main" 162 | optional = true 163 | python-versions = ">=3.6" 164 | files = [ 165 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 166 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 167 | ] 168 | 169 | [package.extras] 170 | dev = ["pre-commit", "tox"] 171 | testing = ["pytest", "pytest-benchmark"] 172 | 173 | [[package]] 174 | name = "pytest" 175 | version = "7.3.1" 176 | description = "pytest: simple powerful testing with Python" 177 | category = "main" 178 | optional = true 179 | python-versions = ">=3.7" 180 | files = [ 181 | {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, 182 | {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, 183 | ] 184 | 185 | [package.dependencies] 186 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 187 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 188 | iniconfig = "*" 189 | packaging = "*" 190 | pluggy = ">=0.12,<2.0" 191 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 192 | 193 | [package.extras] 194 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 195 | 196 | [[package]] 197 | name = "pytest-cov" 198 | version = "4.0.0" 199 | description = "Pytest plugin for measuring coverage." 200 | category = "main" 201 | optional = true 202 | python-versions = ">=3.6" 203 | files = [ 204 | {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, 205 | {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, 206 | ] 207 | 208 | [package.dependencies] 209 | coverage = {version = ">=5.2.1", extras = ["toml"]} 210 | pytest = ">=4.6" 211 | 212 | [package.extras] 213 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] 214 | 215 | [[package]] 216 | name = "ruff" 217 | version = "0.0.259" 218 | description = "An extremely fast Python linter, written in Rust." 219 | category = "main" 220 | optional = false 221 | python-versions = ">=3.7" 222 | files = [ 223 | {file = "ruff-0.0.259-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:f3938dc45e2a3f818e9cbd53007265c22246fbfded8837b2c563bf0ebde1a226"}, 224 | {file = "ruff-0.0.259-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:22e1e35bf5f12072cd644d22afd9203641ccf258bc14ff91aa1c43dc14f6047d"}, 225 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2fb20e89e85d147c85caa807707a1488bccc1f3854dc3d53533e89b52a0c5ff"}, 226 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49e903bcda19f6bb0725a962c058eb5d61f40d84ef52ed53b61939b69402ab4e"}, 227 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71f0ef1985e9a6696fa97da8459917fa34bdaa2c16bd33bd5edead585b7d44f7"}, 228 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7cfef26619cba184d59aa7fa17b48af5891d51fc0b755a9bc533478a10d4d066"}, 229 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79b02fa17ec1fd8d306ae302cb47fb614b71e1f539997858243769bcbe78c6d9"}, 230 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:428507fb321b386dda70d66cd1a8aa0abf51d7c197983d83bb9e4fa5ee60300b"}, 231 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5fbaea9167f1852757f02133e5daacdb8c75b3431343205395da5b10499927a"}, 232 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:40ae87f2638484b7e8a7567b04a7af719f1c484c5bf132038b702bb32e1f6577"}, 233 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:29e2b77b7d5da6a7dd5cf9b738b511355c5734ece56f78e500d4b5bffd58c1a0"}, 234 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b3c1beacf6037e7f0781d4699d9a2dd4ba2462f475be5b1f45cf84c4ba3c69d"}, 235 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:daaea322e7e85f4c13d82be9536309e1c4b8b9851bb0cbc7eeb15d490fd46bf9"}, 236 | {file = "ruff-0.0.259-py3-none-win32.whl", hash = "sha256:38704f151323aa5858370a2f792e122cc25e5d1aabe7d42ceeab83da18f0b456"}, 237 | {file = "ruff-0.0.259-py3-none-win_amd64.whl", hash = "sha256:aa9449b898287e621942cc71b9327eceb8f0c357e4065fecefb707ef2d978df8"}, 238 | {file = "ruff-0.0.259-py3-none-win_arm64.whl", hash = "sha256:e4f39e18702de69faaaee3969934b92d7467285627f99a5b6ecd55a7d9f5d086"}, 239 | {file = "ruff-0.0.259.tar.gz", hash = "sha256:8b56496063ab3bfdf72339a5fbebb8bd46e5c5fee25ef11a9f03b208fa0562ec"}, 240 | ] 241 | 242 | [[package]] 243 | name = "tomli" 244 | version = "2.0.1" 245 | description = "A lil' TOML parser" 246 | category = "main" 247 | optional = true 248 | python-versions = ">=3.7" 249 | files = [ 250 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 251 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 252 | ] 253 | 254 | [extras] 255 | bells = ["app-c"] 256 | whistles = ["app-b", "coverage"] 257 | 258 | [metadata] 259 | lock-version = "2.0" 260 | python-versions = "^3.10" 261 | content-hash = "150692452bb45390ff1a80d4f3181acb377d6a8f65061315f158f24733d6d561" 262 | -------------------------------------------------------------------------------- /tests/fixtures/nested_packages/others/app_c/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "app-b" 5 | version = "0.1" 6 | description = "lorem ipsum" 7 | optional = false 8 | python-versions = "^3.8" 9 | groups = ["dev"] 10 | files = [] 11 | develop = true 12 | 13 | [package.dependencies] 14 | pytest = "^7.1" 15 | ruff = "^0.0.259" 16 | 17 | [package.source] 18 | type = "directory" 19 | url = "../.." 20 | 21 | [[package]] 22 | name = "attrs" 23 | version = "22.2.0" 24 | description = "Classes Without Boilerplate" 25 | optional = false 26 | python-versions = ">=3.6" 27 | groups = ["main", "dev"] 28 | files = [ 29 | {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, 30 | {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, 31 | ] 32 | 33 | [package.extras] 34 | cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] 35 | dev = ["attrs[docs,tests]"] 36 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] 37 | tests = ["attrs[tests-no-zope]", "zope.interface"] 38 | tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] 39 | 40 | [[package]] 41 | name = "colorama" 42 | version = "0.4.6" 43 | description = "Cross-platform colored terminal text." 44 | optional = false 45 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 46 | groups = ["main", "dev"] 47 | markers = "sys_platform == \"win32\"" 48 | files = [ 49 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 50 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 51 | ] 52 | 53 | [[package]] 54 | name = "coverage" 55 | version = "7.2.1" 56 | description = "Code coverage measurement for Python" 57 | optional = false 58 | python-versions = ">=3.7" 59 | groups = ["main"] 60 | files = [ 61 | {file = "coverage-7.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49567ec91fc5e0b15356da07a2feabb421d62f52a9fff4b1ec40e9e19772f5f8"}, 62 | {file = "coverage-7.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2ef6cae70168815ed91388948b5f4fcc69681480a0061114db737f957719f03"}, 63 | {file = "coverage-7.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3004765bca3acd9e015794e5c2f0c9a05587f5e698127ff95e9cfba0d3f29339"}, 64 | {file = "coverage-7.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cca7c0b7f5881dfe0291ef09ba7bb1582cb92ab0aeffd8afb00c700bf692415a"}, 65 | {file = "coverage-7.2.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2167d116309f564af56f9aa5e75ef710ef871c5f9b313a83050035097b56820"}, 66 | {file = "coverage-7.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cb5f152fb14857cbe7f3e8c9a5d98979c4c66319a33cad6e617f0067c9accdc4"}, 67 | {file = "coverage-7.2.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:87dc37f16fb5e3a28429e094145bf7c1753e32bb50f662722e378c5851f7fdc6"}, 68 | {file = "coverage-7.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e191a63a05851f8bce77bc875e75457f9b01d42843f8bd7feed2fc26bbe60833"}, 69 | {file = "coverage-7.2.1-cp310-cp310-win32.whl", hash = "sha256:e3ea04b23b114572b98a88c85379e9e9ae031272ba1fb9b532aa934c621626d4"}, 70 | {file = "coverage-7.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:0cf557827be7eca1c38a2480484d706693e7bb1929e129785fe59ec155a59de6"}, 71 | {file = "coverage-7.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:570c21a29493b350f591a4b04c158ce1601e8d18bdcd21db136fbb135d75efa6"}, 72 | {file = "coverage-7.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e872b082b32065ac2834149dc0adc2a2e6d8203080501e1e3c3c77851b466f9"}, 73 | {file = "coverage-7.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac6343bae03b176e9b58104a9810df3cdccd5cfed19f99adfa807ffbf43cf9b"}, 74 | {file = "coverage-7.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abacd0a738e71b20e224861bc87e819ef46fedba2fb01bc1af83dfd122e9c319"}, 75 | {file = "coverage-7.2.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9256d4c60c4bbfec92721b51579c50f9e5062c21c12bec56b55292464873508"}, 76 | {file = "coverage-7.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80559eaf6c15ce3da10edb7977a1548b393db36cbc6cf417633eca05d84dd1ed"}, 77 | {file = "coverage-7.2.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0bd7e628f6c3ec4e7d2d24ec0e50aae4e5ae95ea644e849d92ae4805650b4c4e"}, 78 | {file = "coverage-7.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09643fb0df8e29f7417adc3f40aaf379d071ee8f0350ab290517c7004f05360b"}, 79 | {file = "coverage-7.2.1-cp311-cp311-win32.whl", hash = "sha256:1b7fb13850ecb29b62a447ac3516c777b0e7a09ecb0f4bb6718a8654c87dfc80"}, 80 | {file = "coverage-7.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:617a94ada56bbfe547aa8d1b1a2b8299e2ec1ba14aac1d4b26a9f7d6158e1273"}, 81 | {file = "coverage-7.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8649371570551d2fd7dee22cfbf0b61f1747cdfb2b7587bb551e4beaaa44cb97"}, 82 | {file = "coverage-7.2.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d2b9b5e70a21474c105a133ba227c61bc95f2ac3b66861143ce39a5ea4b3f84"}, 83 | {file = "coverage-7.2.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae82c988954722fa07ec5045c57b6d55bc1a0890defb57cf4a712ced65b26ddd"}, 84 | {file = "coverage-7.2.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:861cc85dfbf55a7a768443d90a07e0ac5207704a9f97a8eb753292a7fcbdfcfc"}, 85 | {file = "coverage-7.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0339dc3237c0d31c3b574f19c57985fcbe494280153bbcad33f2cdf469f4ac3e"}, 86 | {file = "coverage-7.2.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5928b85416a388dd557ddc006425b0c37e8468bd1c3dc118c1a3de42f59e2a54"}, 87 | {file = "coverage-7.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8d3843ca645f62c426c3d272902b9de90558e9886f15ddf5efe757b12dd376f5"}, 88 | {file = "coverage-7.2.1-cp37-cp37m-win32.whl", hash = "sha256:6a034480e9ebd4e83d1aa0453fd78986414b5d237aea89a8fdc35d330aa13bae"}, 89 | {file = "coverage-7.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6fce673f79a0e017a4dc35e18dc7bb90bf6d307c67a11ad5e61ca8d42b87cbff"}, 90 | {file = "coverage-7.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f099da6958ddfa2ed84bddea7515cb248583292e16bb9231d151cd528eab657"}, 91 | {file = "coverage-7.2.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:97a3189e019d27e914ecf5c5247ea9f13261d22c3bb0cfcfd2a9b179bb36f8b1"}, 92 | {file = "coverage-7.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a81dbcf6c6c877986083d00b834ac1e84b375220207a059ad45d12f6e518a4e3"}, 93 | {file = "coverage-7.2.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2c3dde4c0b9be4b02067185136b7ee4681978228ad5ec1278fa74f5ca3e99"}, 94 | {file = "coverage-7.2.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a209d512d157379cc9ab697cbdbb4cfd18daa3e7eebaa84c3d20b6af0037384"}, 95 | {file = "coverage-7.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f3d07edb912a978915576a776756069dede66d012baa503022d3a0adba1b6afa"}, 96 | {file = "coverage-7.2.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8dca3c1706670297851bca1acff9618455122246bdae623be31eca744ade05ec"}, 97 | {file = "coverage-7.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b1991a6d64231a3e5bbe3099fb0dd7c9aeaa4275ad0e0aeff4cb9ef885c62ba2"}, 98 | {file = "coverage-7.2.1-cp38-cp38-win32.whl", hash = "sha256:22c308bc508372576ffa3d2dbc4824bb70d28eeb4fcd79d4d1aed663a06630d0"}, 99 | {file = "coverage-7.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:b0c0d46de5dd97f6c2d1b560bf0fcf0215658097b604f1840365296302a9d1fb"}, 100 | {file = "coverage-7.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4dd34a935de268a133e4741827ae951283a28c0125ddcdbcbba41c4b98f2dfef"}, 101 | {file = "coverage-7.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0f8318ed0f3c376cfad8d3520f496946977abde080439d6689d7799791457454"}, 102 | {file = "coverage-7.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:834c2172edff5a08d78e2f53cf5e7164aacabeb66b369f76e7bb367ca4e2d993"}, 103 | {file = "coverage-7.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4d70c853f0546855f027890b77854508bdb4d6a81242a9d804482e667fff6e6"}, 104 | {file = "coverage-7.2.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a6450da4c7afc4534305b2b7d8650131e130610cea448ff240b6ab73d7eab63"}, 105 | {file = "coverage-7.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:99f4dd81b2bb8fc67c3da68b1f5ee1650aca06faa585cbc6818dbf67893c6d58"}, 106 | {file = "coverage-7.2.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bdd3f2f285ddcf2e75174248b2406189261a79e7fedee2ceeadc76219b6faa0e"}, 107 | {file = "coverage-7.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f29351393eb05e6326f044a7b45ed8e38cb4dcc38570d12791f271399dc41431"}, 108 | {file = "coverage-7.2.1-cp39-cp39-win32.whl", hash = "sha256:e2b50ebc2b6121edf352336d503357321b9d8738bb7a72d06fc56153fd3f4cd8"}, 109 | {file = "coverage-7.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd5a12239c0006252244f94863f1c518ac256160cd316ea5c47fb1a11b25889a"}, 110 | {file = "coverage-7.2.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:436313d129db7cf5b4ac355dd2bd3f7c7e5294af077b090b85de75f8458b8616"}, 111 | {file = "coverage-7.2.1.tar.gz", hash = "sha256:c77f2a9093ccf329dd523a9b2b3c854c20d2a3d968b6def3b820272ca6732242"}, 112 | ] 113 | 114 | [package.dependencies] 115 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 116 | 117 | [package.extras] 118 | toml = ["tomli"] 119 | 120 | [[package]] 121 | name = "exceptiongroup" 122 | version = "1.1.0" 123 | description = "Backport of PEP 654 (exception groups)" 124 | optional = false 125 | python-versions = ">=3.7" 126 | groups = ["main", "dev"] 127 | markers = "python_version < \"3.11\"" 128 | files = [ 129 | {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, 130 | {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, 131 | ] 132 | 133 | [package.extras] 134 | test = ["pytest (>=6)"] 135 | 136 | [[package]] 137 | name = "iniconfig" 138 | version = "2.0.0" 139 | description = "brain-dead simple config-ini parsing" 140 | optional = false 141 | python-versions = ">=3.7" 142 | groups = ["main", "dev"] 143 | files = [ 144 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 145 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 146 | ] 147 | 148 | [[package]] 149 | name = "packaging" 150 | version = "23.0" 151 | description = "Core utilities for Python packages" 152 | optional = false 153 | python-versions = ">=3.7" 154 | groups = ["main", "dev"] 155 | files = [ 156 | {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, 157 | {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, 158 | ] 159 | 160 | [[package]] 161 | name = "pluggy" 162 | version = "1.0.0" 163 | description = "plugin and hook calling mechanisms for python" 164 | optional = false 165 | python-versions = ">=3.6" 166 | groups = ["main", "dev"] 167 | files = [ 168 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 169 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 170 | ] 171 | 172 | [package.extras] 173 | dev = ["pre-commit", "tox"] 174 | testing = ["pytest", "pytest-benchmark"] 175 | 176 | [[package]] 177 | name = "pytest" 178 | version = "7.2.2" 179 | description = "pytest: simple powerful testing with Python" 180 | optional = false 181 | python-versions = ">=3.7" 182 | groups = ["main", "dev"] 183 | files = [ 184 | {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, 185 | {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, 186 | ] 187 | 188 | [package.dependencies] 189 | attrs = ">=19.2.0" 190 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 191 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 192 | iniconfig = "*" 193 | packaging = "*" 194 | pluggy = ">=0.12,<2.0" 195 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 196 | 197 | [package.extras] 198 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 199 | 200 | [[package]] 201 | name = "pytest-cov" 202 | version = "4.0.0" 203 | description = "Pytest plugin for measuring coverage." 204 | optional = false 205 | python-versions = ">=3.6" 206 | groups = ["main"] 207 | files = [ 208 | {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, 209 | {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, 210 | ] 211 | 212 | [package.dependencies] 213 | coverage = {version = ">=5.2.1", extras = ["toml"]} 214 | pytest = ">=4.6" 215 | 216 | [package.extras] 217 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] 218 | 219 | [[package]] 220 | name = "ruff" 221 | version = "0.0.259" 222 | description = "An extremely fast Python linter, written in Rust." 223 | optional = false 224 | python-versions = ">=3.7" 225 | groups = ["dev"] 226 | files = [ 227 | {file = "ruff-0.0.259-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:f3938dc45e2a3f818e9cbd53007265c22246fbfded8837b2c563bf0ebde1a226"}, 228 | {file = "ruff-0.0.259-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:22e1e35bf5f12072cd644d22afd9203641ccf258bc14ff91aa1c43dc14f6047d"}, 229 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2fb20e89e85d147c85caa807707a1488bccc1f3854dc3d53533e89b52a0c5ff"}, 230 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49e903bcda19f6bb0725a962c058eb5d61f40d84ef52ed53b61939b69402ab4e"}, 231 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71f0ef1985e9a6696fa97da8459917fa34bdaa2c16bd33bd5edead585b7d44f7"}, 232 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7cfef26619cba184d59aa7fa17b48af5891d51fc0b755a9bc533478a10d4d066"}, 233 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79b02fa17ec1fd8d306ae302cb47fb614b71e1f539997858243769bcbe78c6d9"}, 234 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:428507fb321b386dda70d66cd1a8aa0abf51d7c197983d83bb9e4fa5ee60300b"}, 235 | {file = "ruff-0.0.259-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5fbaea9167f1852757f02133e5daacdb8c75b3431343205395da5b10499927a"}, 236 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:40ae87f2638484b7e8a7567b04a7af719f1c484c5bf132038b702bb32e1f6577"}, 237 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:29e2b77b7d5da6a7dd5cf9b738b511355c5734ece56f78e500d4b5bffd58c1a0"}, 238 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b3c1beacf6037e7f0781d4699d9a2dd4ba2462f475be5b1f45cf84c4ba3c69d"}, 239 | {file = "ruff-0.0.259-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:daaea322e7e85f4c13d82be9536309e1c4b8b9851bb0cbc7eeb15d490fd46bf9"}, 240 | {file = "ruff-0.0.259-py3-none-win32.whl", hash = "sha256:38704f151323aa5858370a2f792e122cc25e5d1aabe7d42ceeab83da18f0b456"}, 241 | {file = "ruff-0.0.259-py3-none-win_amd64.whl", hash = "sha256:aa9449b898287e621942cc71b9327eceb8f0c357e4065fecefb707ef2d978df8"}, 242 | {file = "ruff-0.0.259-py3-none-win_arm64.whl", hash = "sha256:e4f39e18702de69faaaee3969934b92d7467285627f99a5b6ecd55a7d9f5d086"}, 243 | {file = "ruff-0.0.259.tar.gz", hash = "sha256:8b56496063ab3bfdf72339a5fbebb8bd46e5c5fee25ef11a9f03b208fa0562ec"}, 244 | ] 245 | 246 | [[package]] 247 | name = "tomli" 248 | version = "2.0.1" 249 | description = "A lil' TOML parser" 250 | optional = false 251 | python-versions = ">=3.7" 252 | groups = ["main", "dev"] 253 | files = [ 254 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 255 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 256 | ] 257 | markers = {main = "python_full_version <= \"3.11.0a6\"", dev = "python_version < \"3.11\""} 258 | 259 | [[package]] 260 | name = "urllib3" 261 | version = "1.26.20" 262 | description = "HTTP library with thread-safe connection pooling, file post, and more." 263 | optional = false 264 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" 265 | groups = ["main"] 266 | markers = "python_version < \"3.11\"" 267 | files = [ 268 | {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"}, 269 | {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"}, 270 | ] 271 | 272 | [package.extras] 273 | brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 274 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 275 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 276 | 277 | [[package]] 278 | name = "urllib3" 279 | version = "2.3.0" 280 | description = "HTTP library with thread-safe connection pooling, file post, and more." 281 | optional = false 282 | python-versions = ">=3.9" 283 | groups = ["main"] 284 | markers = "python_version >= \"3.11\"" 285 | files = [ 286 | {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, 287 | {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, 288 | ] 289 | 290 | [package.extras] 291 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 292 | h2 = ["h2 (>=4,<5)"] 293 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 294 | zstd = ["zstandard (>=0.18.0)"] 295 | 296 | [metadata] 297 | lock-version = "2.1" 298 | python-versions = "^3.10" 299 | content-hash = "75e85df585cc3f92fd5717f13d4bdd4799fc8898b7bfd27564fc47ab55b834c9" 300 | --------------------------------------------------------------------------------