diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..4cd29a6 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Migrate to ruff formatter (replaces black) +fdf3066e3346c925faaf8fdc3746698588d77dad diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5b70917..9bb6e21 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,17 +1,14 @@ -# Automatically update versions for pip and npm - +# Automatically update versions for uv and GitHub Actions version: 2 updates: - - # Maintain dependencies for Python - - package-ecosystem: "pip" + # Maintain dependencies for uv + - package-ecosystem: "uv" directory: "/" schedule: interval: "monthly" time: "00:00" cooldown: default-days: 7 - # Maintain dependencies for Github Actions - package-ecosystem: "github-actions" directory: "/" diff --git a/.github/workflows/call-contributor-issue-comment.yml b/.github/workflows/call-contributor-issue-comment.yml index 4e8b7db..a9512b4 100644 --- a/.github/workflows/call-contributor-issue-comment.yml +++ b/.github/workflows/call-contributor-issue-comment.yml @@ -1,9 +1,7 @@ name: Handle contributor comment on GitHub issue - on: issue_comment: types: [created] - jobs: call-workflow: uses: learningequality/.github/.github/workflows/contributor-issue-comment.yml@main diff --git a/.github/workflows/call-contributor-pr-reply.yml b/.github/workflows/call-contributor-pr-reply.yml index 273a604..e8316e0 100644 --- a/.github/workflows/call-contributor-pr-reply.yml +++ b/.github/workflows/call-contributor-pr-reply.yml @@ -2,7 +2,6 @@ name: Send reply on a new contributor pull request on: pull_request_target: types: [opened] - jobs: call-workflow: name: Call shared workflow diff --git a/.github/workflows/call-manage-issue-header.yml b/.github/workflows/call-manage-issue-header.yml index 4af6730..845a679 100644 --- a/.github/workflows/call-manage-issue-header.yml +++ b/.github/workflows/call-manage-issue-header.yml @@ -1,13 +1,11 @@ name: Manage issue header - on: issues: types: [opened, reopened, labeled, unlabeled] - jobs: call-workflow: name: Call shared workflow uses: learningequality/.github/.github/workflows/manage-issue-header.yml@main secrets: - LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }} - LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }} + LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }} + LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }} diff --git a/.github/workflows/call-update-pr-spreadsheet.yml b/.github/workflows/call-update-pr-spreadsheet.yml index f752b03..837a34e 100644 --- a/.github/workflows/call-update-pr-spreadsheet.yml +++ b/.github/workflows/call-update-pr-spreadsheet.yml @@ -1,9 +1,7 @@ name: Update community pull requests spreadsheet - on: pull_request_target: types: [assigned, unassigned, opened, closed, reopened, edited, review_requested, review_request_removed] - jobs: call-workflow: name: Call shared workflow diff --git a/.github/workflows/community-contribution-labeling.yml b/.github/workflows/community-contribution-labeling.yml index 701465b..5bc59f8 100644 --- a/.github/workflows/community-contribution-labeling.yml +++ b/.github/workflows/community-contribution-labeling.yml @@ -1,12 +1,10 @@ name: Community Contribution Label - on: issues: types: [assigned, unassigned] - jobs: call-label-action: uses: learningequality/.github/.github/workflows/community-contribution-label.yml@main secrets: - LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }} - LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }} + LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }} + LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }} diff --git a/.github/workflows/finalized_specs.yml b/.github/workflows/finalized_specs.yml index e007f3d..8754fdd 100644 --- a/.github/workflows/finalized_specs.yml +++ b/.github/workflows/finalized_specs.yml @@ -1,11 +1,9 @@ name: Finalized specs - on: push: branches: - - main + - main pull_request: - jobs: change_check: name: Check if file changed @@ -21,12 +19,12 @@ jobs: - id: changes # Set outputs using the command. run: | - echo "modified=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep spec/labels-v1.json$ | xargs)" >> $GITHUB_OUTPUT + echo "modified=$(git diff --name-only --diff-filter=ACMRT "${{ github.event.pull_request.base.sha }}" "${{ github.sha }}" | grep spec/labels-v1.json$ | xargs)" >> "$GITHUB_OUTPUT" unit_test: name: Error if finalized spec modified needs: change_check if: ${{ needs.change_check.outputs.modified }} runs-on: ubuntu-latest steps: - - name: Fail if specs modified - run: exit 1 + - name: Fail if specs modified + run: exit 1 diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 12ff6bc..44ff8a4 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -1,33 +1,25 @@ name: Publish npmjs Package - on: release: types: [published] - permissions: id-token: write # Required for npm trusted publishing (OIDC) contents: read - jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + with: + fetch-depth: 0 - uses: actions/setup-node@v6 with: node-version: '24' registry-url: https://registry.npmjs.org/ - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: 3.9 - - name: Cache pip - uses: actions/cache@v5 + - name: Set up uv + uses: astral-sh/setup-uv@v7 with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-py3.9-${{ hashFiles('.github/workflows/npm-publish.yml') }} - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install pre-commit - - run: make release-npm + enable-cache: true + cache-python: true + - name: Build and publish + run: make release-npm diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 219e20b..61e533e 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -1,11 +1,9 @@ name: Linting - on: push: branches: - - main + - main pull_request: - jobs: pre_job: name: Path match check @@ -24,7 +22,11 @@ jobs: if: ${{ needs.pre_job.outputs.should_skip != 'true' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 - - run: pip install setuptools - - uses: pre-commit/action@v3.0.1 + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-python: true + - uses: j178/prek-action@v2 diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 634cf65..1aec7ef 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -1,30 +1,24 @@ # This workflow will upload a Python Package using pypa/gh-action-pypi-publish when a release is created - name: Upload Python Package - on: release: types: [published] - jobs: deploy: - - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write - steps: - - uses: actions/checkout@v6 - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: 3.9 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel pre-commit - - name: Build distribution - run: make dist - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-python: true + - name: Build distribution + run: make dist + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/pythontest.yml b/.github/workflows/pythontest.yml index 58ee34c..19a7f41 100644 --- a/.github/workflows/pythontest.yml +++ b/.github/workflows/pythontest.yml @@ -1,11 +1,9 @@ name: Python tests - on: push: branches: - - main + - main pull_request: - jobs: pre_job: name: Path match check @@ -18,7 +16,7 @@ jobs: uses: fkirc/skip-duplicate-actions@master with: github_token: ${{ github.token }} - paths: '["**.py", ".github/workflows/pythontest.yml", "requirements-test.txt", "tox.ini"]' + paths: '["**.py", ".github/workflows/pythontest.yml", "pyproject.toml", "uv.lock"]' unit_test: name: Python unit tests needs: pre_job @@ -28,34 +26,19 @@ jobs: matrix: python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] steps: - - uses: actions/checkout@v6 - if: ${{ needs.pre_job.outputs.should_skip != 'true' }} - - name: Set up Python ${{ matrix.python-version }} - if: ${{ needs.pre_job.outputs.should_skip != 'true' }} - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - - name: Cache pip - if: ${{ needs.pre_job.outputs.should_skip != 'true' }} - uses: actions/cache@v5 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-py${{ matrix.python-version }}-${{ hashFiles('setup.py') }} - - name: Install tox - if: ${{ needs.pre_job.outputs.should_skip != 'true' }} - run: | - python -m pip install --upgrade pip - pip install tox - - name: tox env cache - if: ${{ needs.pre_job.outputs.should_skip != 'true' && !startsWith(runner.os, 'windows') }} - uses: actions/cache@v5 - with: - path: ${{ github.workspace }}/.tox/py${{ matrix.python-version }} - key: ${{ runner.os }}-tox-py${{ matrix.python-version }}-${{ hashFiles('setup.py') }} - - name: Test with tox - if: ${{ needs.pre_job.outputs.should_skip != 'true' }} - run: tox -e py${{ matrix.python-version }} - + - uses: actions/checkout@v6 + if: ${{ needs.pre_job.outputs.should_skip != 'true' }} + with: + fetch-depth: 0 + - name: Set up uv + if: ${{ needs.pre_job.outputs.should_skip != 'true' }} + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-python: true + - name: Run tests + if: ${{ needs.pre_job.outputs.should_skip != 'true' }} + run: uv run --python ${{ matrix.python-version }} --group test pytest unit_test_eol_python: name: Python unit tests for EOL Python versions needs: pre_job @@ -67,25 +50,13 @@ jobs: container: image: python:${{ matrix.python-version }}-buster steps: - - uses: actions/checkout@v6 - if: ${{ needs.pre_job.outputs.should_skip != 'true' }} - - name: Cache pip - if: ${{ needs.pre_job.outputs.should_skip != 'true' }} - uses: actions/cache@v5 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-py${{ matrix.python-version }}-${{ hashFiles('setup.py') }} - - name: Install tox - if: ${{ needs.pre_job.outputs.should_skip != 'true' }} - run: | - python -m pip install --upgrade pip - pip install tox - - name: tox env cache - if: ${{ needs.pre_job.outputs.should_skip != 'true' && !startsWith(runner.os, 'windows') }} - uses: actions/cache@v5 - with: - path: ${{ github.workspace }}/.tox/py${{ matrix.python-version }} - key: ${{ runner.os }}-tox-py${{ matrix.python-version }}-${{ hashFiles('setup.py') }} - - name: Test with tox - if: ${{ needs.pre_job.outputs.should_skip != 'true' }} - run: tox -e py${{ matrix.python-version }} + - uses: actions/checkout@v6 + if: ${{ needs.pre_job.outputs.should_skip != 'true' }} + - name: Install dependencies and run tests + if: ${{ needs.pre_job.outputs.should_skip != 'true' }} + env: + PYTHONPATH: ${{ github.workspace }} + run: | + python -m pip install --upgrade pip + pip install "pytest>=6.2.5,<8" "jsonschema==3.2.0" + pytest diff --git a/.github/workflows/unassign-inactive.yaml b/.github/workflows/unassign-inactive.yaml index 4151166..bfb87ca 100644 --- a/.github/workflows/unassign-inactive.yaml +++ b/.github/workflows/unassign-inactive.yaml @@ -1,11 +1,9 @@ name: "Unassign Inactive Contributors" run-name: Unassign Inactive Contributors - on: schedule: - cron: "1 0 * * 1" # Every Monday at 00:01 UTC workflow_dispatch: - jobs: unassign-inactive: uses: learningequality/.github/.github/workflows/unassign-inactive-issues.yaml@main diff --git a/.gitignore b/.gitignore index 05f4cb8..2760b06 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,7 @@ celerybeat-schedule # virtualenv venv/ +.venv/ venv2/ ENV/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 20726dc..025f6d4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,28 +1,38 @@ -exclude: (\.git/|\.tox/|\.venv/|le_utils\.egg-info) +exclude: (\.git/|\.venv/|le_utils\.egg-info) repos: -- repo: local + - repo: local hooks: - - id: rebuild-from-specs + - id: rebuild-from-specs name: Regenerate files description: Regenerates files when specs or version has changed entry: make build language: system - files: (spec/.*\.json|setup\.py)$ -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.0.0 + pass_filenames: false + files: (spec/.*\.json)$ + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 hooks: - - id: trailing-whitespace - - id: flake8 - - id: check-yaml - - id: check-added-large-files - - id: debug-statements - - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-yaml + - id: check-added-large-files + - id: debug-statements + - id: end-of-file-fixer exclude: '^.+?(\.json|\.po)$' -- repo: https://github.com/asottile/reorder_python_imports - rev: v1.3.3 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.7 hooks: - - id: reorder-python-imports -- repo: https://github.com/python/black - rev: 22.3.0 + - id: ruff + args: [--fix] + - id: ruff-format + - repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.11.2 hooks: - - id: black + - id: uv-lock + - repo: https://github.com/google/yamlfmt + rev: v0.16.0 + hooks: + - id: yamlfmt + - repo: https://github.com/rhysd/actionlint + rev: v1.7.7 + hooks: + - id: actionlint diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 046a397..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include README.md LICENSE.txt diff --git a/Makefile b/Makefile index 15e3626..0c01658 100644 --- a/Makefile +++ b/Makefile @@ -16,25 +16,24 @@ clean-pyc: ## remove Python file artifacts find . -name '__pycache__' -exec rm -fr {} + clean-test: ## remove test and coverage artifacts - rm -fr .tox/ rm -f .coverage rm -fr htmlcov/ test: - pytest -s + uv run pytest -s build: - pip install -e . - python scripts/generate_from_specs.py + uv sync --group dev + uv run python scripts/generate_from_specs.py dist: clean build - python setup.py sdist + uv build release: dist - twine upload dist/*.tar.gz + uv publish release-npm: clean build cd js && npm publish add-language: - python scripts/add_language.py + uv run python scripts/add_language.py diff --git a/js/package.json b/js/package.json index 2ee4cf1..602d2a4 100644 --- a/js/package.json +++ b/js/package.json @@ -27,5 +27,5 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "version": "0.2.16" + "version": "0.2.17" } \ No newline at end of file diff --git a/le_utils/constants/completion_criteria.py b/le_utils/constants/completion_criteria.py index 84c0b5e..eb2f8cf 100644 --- a/le_utils/constants/completion_criteria.py +++ b/le_utils/constants/completion_criteria.py @@ -39,33 +39,16 @@ "model": { "type": "string", "$exportConstants": "completion_criteria", - "enum": [ - "time", - "approx_time", - "pages", - "mastery", - "reference", - "determined_by_resource", - ], + "enum": ["time", "approx_time", "pages", "mastery", "reference", "determined_by_resource"], }, "mastery_criteria": {"$ref": "/schemas/mastery_criteria"}, }, - "properties": { - "model": {"$ref": "#/definitions/model"}, - "learner_managed": {"type": "boolean"}, - "threshold": True, - }, + "properties": {"model": {"$ref": "#/definitions/model"}, "learner_managed": {"type": "boolean"}, "threshold": True}, "required": ["model"], "anyOf": [ { "properties": { - "model": { - "anyOf": [ - {"const": "time"}, - {"const": "approx_time"}, - {"const": "pages"}, - ] - }, + "model": {"anyOf": [{"const": "time"}, {"const": "approx_time"}, {"const": "pages"}]}, "threshold": {"type": "number", "exclusiveMinimum": 0}, }, "required": ["threshold"], @@ -73,34 +56,11 @@ { "properties": { "model": {"const": "pages"}, - "threshold": { - "type": "string", - "pattern": "^(100|[1-9][0-9]?)%$", - "description": "A percentage", - "minLength": 2, - "maxLength": 4, - }, - }, - "required": ["threshold"], - }, - { - "properties": { - "model": {"const": "mastery"}, - "threshold": {"$ref": "#/definitions/mastery_criteria"}, + "threshold": {"type": "string", "pattern": "^(100|[1-9][0-9]?)%$", "description": "A percentage", "minLength": 2, "maxLength": 4}, }, "required": ["threshold"], }, - { - "properties": { - "model": { - "anyOf": [ - {"const": "reference"}, - {"const": "determined_by_resource"}, - ] - }, - "threshold": {"type": "null"}, - }, - "required": [], - }, + {"properties": {"model": {"const": "mastery"}, "threshold": {"$ref": "#/definitions/mastery_criteria"}}, "required": ["threshold"]}, + {"properties": {"model": {"anyOf": [{"const": "reference"}, {"const": "determined_by_resource"}]}, "threshold": {"type": "null"}}, "required": []}, ], } diff --git a/le_utils/constants/content_kinds.py b/le_utils/constants/content_kinds.py index 1ffe2dd..b2896c2 100644 --- a/le_utils/constants/content_kinds.py +++ b/le_utils/constants/content_kinds.py @@ -58,9 +58,7 @@ def generate_list(constantlist): def _initialize_kind_list(): - constantlist = json.loads( - pkgutil.get_data("le_utils", "resources/kindlookup.json").decode("utf-8") - ) + constantlist = json.loads(pkgutil.get_data("le_utils", "resources/kindlookup.json").decode("utf-8")) return generate_list(constantlist) diff --git a/le_utils/constants/embed_content_request.py b/le_utils/constants/embed_content_request.py index 69df758..429a169 100644 --- a/le_utils/constants/embed_content_request.py +++ b/le_utils/constants/embed_content_request.py @@ -26,10 +26,7 @@ "description": "Language code from https://github.com/learningequality/le-utils/blob/main/le_utils/resources/languagelookup.json", "pattern": "^[a-z]{2,3}(?:-[a-zA-Z]+)*$", }, - "url": { - "type": "string", - "pattern": "^(https?:\\/\\/(?:storage\\.cloud\\.google\\.com|localhost(?::[0-9]+)?)\\/[a-z0-9-._~!$&'()*+,;=:@%\\/\\?]+)$", - }, + "url": {"type": "string", "pattern": "^(https?:\\/\\/(?:storage\\.cloud\\.google\\.com|localhost(?::[0-9]+)?)\\/[a-z0-9-._~!$&'()*+,;=:@%\\/\\?]+)$"}, "preset": { "type": "string", "description": "Presets from https://github.com/learningequality/le-utils/blob/main/le_utils/constants/format_presets.py", @@ -70,11 +67,7 @@ "file": { "type": "object", "additionalProperties": False, - "properties": { - "url": {"$ref": "#/definitions/url"}, - "preset": {"$ref": "#/definitions/preset"}, - "language": {"$ref": "#/definitions/language"}, - }, + "properties": {"url": {"$ref": "#/definitions/url"}, "preset": {"$ref": "#/definitions/preset"}, "language": {"$ref": "#/definitions/language"}}, "required": ["url", "preset"], }, "resource": { @@ -82,61 +75,22 @@ "description": "The key textual metadata and data for a content resource", "additionalProperties": False, "properties": { - "id": { - "$ref": "#/definitions/uuid", - "description": "The UUID of the content resource", - }, - "channel_id": { - "$ref": "#/definitions/uuid", - "description": "The UUID of the channel that the content resource belongs to", - }, - "title": { - "type": "string", - "description": "The title of the content resource", - }, - "description": { - "type": "string", - "description": "The description of the content resource", - }, - "text": { - "type": "string", - "description": "Optional textual content to include in the embedding", - }, + "id": {"$ref": "#/definitions/uuid", "description": "The UUID of the content resource"}, + "channel_id": {"$ref": "#/definitions/uuid", "description": "The UUID of the channel that the content resource belongs to"}, + "title": {"type": "string", "description": "The title of the content resource"}, + "description": {"type": "string", "description": "The description of the content resource"}, + "text": {"type": "string", "description": "Optional textual content to include in the embedding"}, "language": {"$ref": "#/definitions/language"}, - "files": { - "type": "array", - "description": "A list of files associated with the content resource", - "items": {"$ref": "#/definitions/file"}, - }, - "content_id": { - "$ref": "#/definitions/uuid", - "description": "The UUID of the content resource(s)", - }, - "channel_version": { - "type": "integer", - "description": "The version number of the channel that the content resource belongs to", - }, + "files": {"type": "array", "description": "A list of files associated with the content resource", "items": {"$ref": "#/definitions/file"}}, + "content_id": {"$ref": "#/definitions/uuid", "description": "The UUID of the content resource(s)"}, + "channel_version": {"type": "integer", "description": "The version number of the channel that the content resource belongs to"}, }, - "required": [ - "id", - "channel_id", - "title", - "description", - "content_id", - "channel_version", - ], + "required": ["id", "channel_id", "title", "description", "content_id", "channel_version"], }, }, "properties": { - "resources": { - "type": "array", - "description": "A list of content resources to embed", - "items": {"$ref": "#/definitions/resource"}, - }, - "metadata": { - "type": "object", - "description": "The metadata of the channel for logging purposes", - }, + "resources": {"type": "array", "description": "A list of content resources to embed", "items": {"$ref": "#/definitions/resource"}}, + "metadata": {"type": "object", "description": "The metadata of the channel for logging purposes"}, }, "required": ["resources"], } diff --git a/le_utils/constants/embed_topics_request.py b/le_utils/constants/embed_topics_request.py index 0384899..40210d3 100644 --- a/le_utils/constants/embed_topics_request.py +++ b/le_utils/constants/embed_topics_request.py @@ -22,28 +22,19 @@ "description": "A unique identifier in the form of a UUID", }, "title": {"type": "string", "description": "The title of the topic"}, - "description": { - "type": "string", - "description": "The description of the topic", - }, + "description": {"type": "string", "description": "The description of the topic"}, "language": { "type": "string", "description": "Language code from https://github.com/learningequality/le-utils/blob/main/le_utils/resources/languagelookup.json", "pattern": "^[a-z]{2,3}(?:-[a-zA-Z]+)*$", }, - "level": { - "type": "integer", - "description": "The level of the ancestor, where the root is 0 and the parent is the highest level", - }, + "level": {"type": "integer", "description": "The level of the ancestor, where the root is 0 and the parent is the highest level"}, "ancestor": { "type": "object", "description": "An ancestor in the tree structure", "additionalProperties": False, "properties": { - "id": { - "$ref": "#/definitions/uuid", - "description": "The ID of the topic content node on Studio", - }, + "id": {"$ref": "#/definitions/uuid", "description": "The ID of the topic content node on Studio"}, "title": {"$ref": "#/definitions/title"}, "description": {"$ref": "#/definitions/description"}, "language": {"$ref": "#/definitions/language"}, @@ -61,14 +52,8 @@ "description": "A topic in the tree structure", "additionalProperties": False, "properties": { - "id": { - "$ref": "#/definitions/uuid", - "description": "The ID of the topic content node on Studio", - }, - "channel_id": { - "$ref": "#/definitions/uuid", - "description": "The UUID of the channel that the topic belongs to", - }, + "id": {"$ref": "#/definitions/uuid", "description": "The ID of the topic content node on Studio"}, + "channel_id": {"$ref": "#/definitions/uuid", "description": "The UUID of the channel that the topic belongs to"}, "title": {"$ref": "#/definitions/title"}, "description": {"$ref": "#/definitions/description"}, "language": {"$ref": "#/definitions/language"}, @@ -78,15 +63,8 @@ }, }, "properties": { - "topics": { - "type": "array", - "description": "A list of topics to embed", - "items": {"$ref": "#/definitions/topic"}, - }, - "metadata": { - "type": "object", - "description": "The metadata of the channel for logging purposes", - }, + "topics": {"type": "array", "description": "A list of topics to embed", "items": {"$ref": "#/definitions/topic"}}, + "metadata": {"type": "object", "description": "The metadata of the channel for logging purposes"}, }, "required": ["topics"], } diff --git a/le_utils/constants/exercises.py b/le_utils/constants/exercises.py index 3be2f11..3b88b77 100644 --- a/le_utils/constants/exercises.py +++ b/le_utils/constants/exercises.py @@ -1,5 +1,6 @@ # coding=utf-8 -""" Mastery Models """ +"""Mastery Models""" + DO_ALL = "do_all" NUM_CORRECT_IN_A_ROW_10 = "num_correct_in_a_row_10" NUM_CORRECT_IN_A_ROW_2 = "num_correct_in_a_row_2" diff --git a/le_utils/constants/file_formats.py b/le_utils/constants/file_formats.py index 170d368..efda9ac 100644 --- a/le_utils/constants/file_formats.py +++ b/le_utils/constants/file_formats.py @@ -119,9 +119,7 @@ def generate_list(constantlist): def _initialize_format_list(): - constantlist = json.loads( - pkgutil.get_data("le_utils", "resources/formatlookup.json").decode("utf-8") - ) + constantlist = json.loads(pkgutil.get_data("le_utils", "resources/formatlookup.json").decode("utf-8")) return generate_list(constantlist) diff --git a/le_utils/constants/format_presets.py b/le_utils/constants/format_presets.py index 8ea4213..decfe52 100644 --- a/le_utils/constants/format_presets.py +++ b/le_utils/constants/format_presets.py @@ -27,9 +27,7 @@ AUDIO_DEPENDENCY_READABLE = "audio (dependency)" DOCUMENT = "document" -DOCUMENT_READABLE = ( - "Document" # TODO(ivan): Change to "PDF Document" str translations? -) +DOCUMENT_READABLE = "Document" # TODO(ivan): Change to "PDF Document" str translations? EPUB = "epub" EPUB_READABLE = "ePub Document" DOCUMENT_THUMBNAIL = "document_thumbnail" @@ -155,9 +153,7 @@ def generate_list(constantlist): def _initialize_preset_list(): - constantlist = json.loads( - pkgutil.get_data("le_utils", "resources/presetlookup.json").decode("utf-8") - ) + constantlist = json.loads(pkgutil.get_data("le_utils", "resources/presetlookup.json").decode("utf-8")) return generate_list(constantlist) diff --git a/le_utils/constants/labels/subjects.py b/le_utils/constants/labels/subjects.py index ac58a7d..bfcdb6e 100644 --- a/le_utils/constants/labels/subjects.py +++ b/le_utils/constants/labels/subjects.py @@ -86,10 +86,7 @@ (FINANCIAL_LITERACY, "Financial Literacy"), (FOR_TEACHERS, "For Teachers"), (FOUNDATIONS, "Foundations"), - ( - FOUNDATIONS_LOGIC_AND_CRITICAL_THINKING, - "Foundations Logic And Critical Thinking", - ), + (FOUNDATIONS_LOGIC_AND_CRITICAL_THINKING, "Foundations Logic And Critical Thinking"), (GEOMETRY, "Geometry"), (GUIDES, "Guides"), (HISTORY, "History"), diff --git a/le_utils/constants/languages.py b/le_utils/constants/languages.py index 5046334..3640c68 100644 --- a/le_utils/constants/languages.py +++ b/le_utils/constants/languages.py @@ -3,7 +3,6 @@ import pkgutil from collections import namedtuple - logger = logging.getLogger("le_utils") logger.setLevel(logging.INFO) @@ -16,17 +15,11 @@ ) -class Language( - namedtuple( - "Language", ["native_name", "primary_code", "subcode", "name", "text_direction"] - ) -): +class Language(namedtuple("Language", ["native_name", "primary_code", "subcode", "name", "text_direction"])): @property def code(self): if self.subcode: - return "{primary_code}-{subcode}".format( - primary_code=self.primary_code, subcode=self.subcode - ) + return "{primary_code}-{subcode}".format(primary_code=self.primary_code, subcode=self.subcode) else: return self.primary_code @@ -47,17 +40,13 @@ def generate_list(constantlist): parts = code.split("-", maxsplit=1) lang["primary_code"] = parts[0] lang["subcode"] = None if len(parts) == 1 else parts[1] - lang["text_direction"] = ( - RTL_LANGUAGE if lang.pop("rtl", False) else LTR_LANGUAGE - ) + lang["text_direction"] = RTL_LANGUAGE if lang.pop("rtl", False) else LTR_LANGUAGE yield Language(**lang) def _initialize_language_list(): - langlist = json.loads( - pkgutil.get_data("le_utils", "resources/languagelookup.json").decode("utf-8") - ) + langlist = json.loads(pkgutil.get_data("le_utils", "resources/languagelookup.json").decode("utf-8")) return generate_list(langlist) @@ -151,26 +140,14 @@ def getlang_by_name(name): if "," in lang_native_name: new_native_names = [n.strip() for n in lang_native_name.split(",")] for new_native_name in new_native_names: - simple_native_name = new_native_name.split("(")[ - 0 - ].strip() # text before any bracket - if ( - simple_native_name in _LANGUAGE_NATIVE_NAME_LOOKUP.keys() - or new_native_name in new_items - ): - logger.debug( - "Skip " + simple_native_name + " because it already exisits" - ) + simple_native_name = new_native_name.split("(")[0].strip() # text before any bracket + if simple_native_name in _LANGUAGE_NATIVE_NAME_LOOKUP.keys() or new_native_name in new_items: + logger.debug("Skip " + simple_native_name + " because it already exisits") else: new_items[simple_native_name] = lang_obj elif "(" in lang_native_name: - simple_native_name = lang_native_name.split("(")[ - 0 - ].strip() # text before any bracket - if ( - simple_native_name in _LANGUAGE_NATIVE_NAME_LOOKUP.keys() - or simple_native_name in new_items - ): + simple_native_name = lang_native_name.split("(")[0].strip() # text before any bracket + if simple_native_name in _LANGUAGE_NATIVE_NAME_LOOKUP.keys() or simple_native_name in new_items: logger.debug("Skip " + simple_native_name + " because it already exisits") else: new_items[simple_native_name] = lang_obj @@ -187,9 +164,7 @@ def getlang_by_native_name(native_name): return direct_match else: simple_native_name = native_name.split(",")[0] # take part before comma - simple_native_name = simple_native_name.split("(")[ - 0 - ].strip() # and before any bracket + simple_native_name = simple_native_name.split("(")[0].strip() # and before any bracket return _LANGUAGE_NATIVE_NAME_LOOKUP.get(simple_native_name, None) diff --git a/le_utils/constants/learning_objectives.py b/le_utils/constants/learning_objectives.py index 0622abe..eede754 100644 --- a/le_utils/constants/learning_objectives.py +++ b/le_utils/constants/learning_objectives.py @@ -21,10 +21,7 @@ "pattern": "^[0-9a-f]{32}$", "description": "A unique identifier in the form of the compact hex representations of a UUID v4 or v5", }, - "learning_objective_id": { - "$ref": "#/definitions/hex-uuid", - "description": "Unique identifier for the Learning Objective", - }, + "learning_objective_id": {"$ref": "#/definitions/hex-uuid", "description": "Unique identifier for the Learning Objective"}, }, "properties": { "learning_objectives": { @@ -41,10 +38,7 @@ "pattern": "^\\s*\\S[\\s\\S]*$", "description": "Human-readable text describing the Learning Objective", }, - "metadata": { - "type": "object", - "description": "Optional metadata associated with the Learning Objective", - }, + "metadata": {"type": "object", "description": "Optional metadata associated with the Learning Objective"}, }, "required": ["id", "text"], }, @@ -54,24 +48,14 @@ "description": "Mapping of assessment question IDs to Learning Objective IDs", "minProperties": 1, "additionalProperties": False, - "patternProperties": { - "^[0-9a-f]{32}$": { - "type": "array", - "items": {"$ref": "#/definitions/learning_objective_id"}, - } - }, + "patternProperties": {"^[0-9a-f]{32}$": {"type": "array", "items": {"$ref": "#/definitions/learning_objective_id"}}}, }, "lesson_objectives": { "type": "object", "description": "Mapping of lesson IDs to Learning Objective IDs", "minProperties": 1, "additionalProperties": False, - "patternProperties": { - "^[0-9a-f]{32}$": { - "type": "array", - "items": {"$ref": "#/definitions/learning_objective_id"}, - } - }, + "patternProperties": {"^[0-9a-f]{32}$": {"type": "array", "items": {"$ref": "#/definitions/learning_objective_id"}}}, }, }, "required": ["learning_objectives", "assessment_objectives", "lesson_objectives"], diff --git a/le_utils/constants/licenses.py b/le_utils/constants/licenses.py index ba9fa07..f2d01e1 100644 --- a/le_utils/constants/licenses.py +++ b/le_utils/constants/licenses.py @@ -52,9 +52,7 @@ def generate_list(constantlist): def _initialize_license_list(): - constantlist = json.loads( - pkgutil.get_data("le_utils", "resources/licenselookup.json").decode("utf-8") - ) + constantlist = json.loads(pkgutil.get_data("le_utils", "resources/licenselookup.json").decode("utf-8")) return generate_list(constantlist) diff --git a/le_utils/constants/mastery_criteria.py b/le_utils/constants/mastery_criteria.py index f74d4b7..d627486 100644 --- a/le_utils/constants/mastery_criteria.py +++ b/le_utils/constants/mastery_criteria.py @@ -77,39 +77,19 @@ "description": "List of assessment item UUIDs for version B of the pre/post test", }, }, - "required": [ - "assessment_item_ids", - "version_a_item_ids", - "version_b_item_ids", - ], + "required": ["assessment_item_ids", "version_a_item_ids", "version_b_item_ids"], }, }, - "properties": { - "m": True, - "n": True, - "mastery_model": {"$ref": "#/definitions/mastery_model"}, - "pre_post_test": {"$ref": "#/definitions/pre_post_test"}, - }, + "properties": {"m": True, "n": True, "mastery_model": {"$ref": "#/definitions/mastery_model"}, "pre_post_test": {"$ref": "#/definitions/pre_post_test"}}, "anyOf": [ {"properties": {"mastery_model": {"const": "m_of_n"}}, "required": ["m", "n"]}, { "properties": { - "mastery_model": { - "enum": [ - "do_all", - "num_correct_in_a_row_2", - "num_correct_in_a_row_3", - "num_correct_in_a_row_5", - "num_correct_in_a_row_10", - ] - }, + "mastery_model": {"enum": ["do_all", "num_correct_in_a_row_2", "num_correct_in_a_row_3", "num_correct_in_a_row_5", "num_correct_in_a_row_10"]}, "m": {"type": "null"}, "n": {"type": "null"}, } }, - { - "properties": {"mastery_model": {"const": "pre_post_test"}}, - "required": ["pre_post_test"], - }, + {"properties": {"mastery_model": {"const": "pre_post_test"}}, "required": ["pre_post_test"]}, ], } diff --git a/le_utils/humanhash.py b/le_utils/humanhash.py index c1b11c8..3f976ce 100644 --- a/le_utils/humanhash.py +++ b/le_utils/humanhash.py @@ -4,12 +4,12 @@ The simplest ways to use this module are the :func:`humanize` and :func:`uuid` functions. For tighter control over the output, see :class:`HumanHasher`. """ + import operator import uuid as uuidlib from argparse import ArgumentError from functools import reduce - DEFAULT_WORDLIST = ( "ack", "alabama", @@ -271,7 +271,6 @@ class HumanHasher(object): - """ Transforms hex digests to human-readable strings. @@ -291,7 +290,6 @@ def __init__(self, wordlist=DEFAULT_WORDLIST): self.wordlist = wordlist def humanize(self, hexdigest, words=4, separator="-"): - """ Humanize a given hexadecimal digest. @@ -304,10 +302,7 @@ def humanize(self, hexdigest, words=4, separator="-"): """ # Gets a list of byte values between 0-255. - bytes = [ - int(x, 16) - for x in list(map("".join, list(zip(hexdigest[::2], hexdigest[1::2])))) - ] + bytes = [int(x, 16) for x in list(map("".join, list(zip(hexdigest[::2], hexdigest[1::2]))))] # Compress an arbitrary number of bytes to `words`. compressed = self.compress(bytes, words) # Map the compressed byte values through the word list. @@ -315,7 +310,6 @@ def humanize(self, hexdigest, words=4, separator="-"): @staticmethod def compress(bytes, target): - """ Compress a list of byte values to a fixed target length. @@ -350,7 +344,6 @@ def checksum(bytes): return checksums def uuid(self, **params): - """ Generate a UUID with a human-readable representation. diff --git a/le_utils/proquint.py b/le_utils/proquint.py index 439cfcf..b9350c6 100644 --- a/le_utils/proquint.py +++ b/le_utils/proquint.py @@ -4,6 +4,7 @@ The simplest ways to use this module are the :func:`humanize` and :func:`uuid` functions. For tighter control over the output, see :class:`HumanHasher`. """ + import uuid # Copyright (c) 2014 SUNET. All rights reserved. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a9585b5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,63 @@ +[build-system] +requires = ["setuptools>=78", "setuptools-scm>=8"] +build-backend = "setuptools.build_meta" + +[project] +name = "le-utils" +dynamic = ["version"] +description = "LE-Utils contains shared constants used in Kolibri, Ricecooker, and Kolibri Studio." +readme = "README.md" +license = "MIT" +requires-python = ">=3.6, <3.15" +authors = [ + { name = "Learning Equality", email = "info@learningequality.org" }, +] +keywords = ["le-utils", "le_utils", "LE", "utils", "kolibri", "studio", "ricecooker", "content", "curation"] +classifiers = [ + "Development Status :: 2 - Pre-Alpha", + "Programming Language :: Python", + "Topic :: Utilities", +] + +[project.urls] +Homepage = "https://github.com/learningequality/le-utils" +Download = "https://github.com/learningequality/le-utils/releases" + +[tool.setuptools.packages.find] +include = ["le_utils*"] + +[tool.setuptools.package-data] +le_utils = ["resources/*.json"] + +[tool.setuptools_scm] + +[dependency-groups] +test = [ + "pytest>=6.2.5,<8", + "jsonschema==3.2.0", +] +dev = [ + { include-group = "test" }, + "prek ; python_version >= '3.8'", + "ruff ; python_version >= '3.8'", +] + +[tool.uv] +# Prevents installing packages published less than 7 days ago (supply chain safety). +exclude-newer = "7 days" + +[tool.ruff] +line-length = 160 +# Project supports >=3.6, but ruff's minimum target is py37. +target-version = "py37" + +[tool.ruff.lint] +select = ["E", "F", "W", "C90", "I"] +ignore = ["E203", "E501", "E741"] + +[tool.ruff.lint.mccabe] +max-complexity = 10 + +[tool.ruff.lint.isort] +known-first-party = ["le_utils"] +combine-as-imports = true diff --git a/requirements-lint.txt b/requirements-lint.txt deleted file mode 100644 index 3ee8622..0000000 --- a/requirements-lint.txt +++ /dev/null @@ -1 +0,0 @@ -pre-commit==4.3.0 diff --git a/requirements-test.txt b/requirements-test.txt deleted file mode 100644 index 1428ed2..0000000 --- a/requirements-test.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=6.2.5,<8 -jsonschema==3.2.0 diff --git a/scripts/add_language.py b/scripts/add_language.py index ab3ee33..29510c2 100644 --- a/scripts/add_language.py +++ b/scripts/add_language.py @@ -4,9 +4,7 @@ import logging import os import sys -from typing import Any -from typing import Dict -from typing import Optional +from typing import Any, Dict, Optional try: import langcodes @@ -15,9 +13,7 @@ print("Required libraries not found. Installing them now...") import subprocess - subprocess.check_call( - [sys.executable, "-m", "pip", "install", "langcodes[data]", "pycountry"] - ) + subprocess.check_call([sys.executable, "-m", "pip", "install", "langcodes[data]", "pycountry"]) import langcodes import pycountry @@ -40,9 +36,7 @@ def save_json_file(data: Dict[str, Any], file_path: str) -> None: def _get_pycountry_language(code): - language = pycountry.languages.get(alpha_2=code) or pycountry.languages.get( - alpha_3=code - ) + language = pycountry.languages.get(alpha_2=code) or pycountry.languages.get(alpha_3=code) if not language: return {} data = { @@ -112,10 +106,7 @@ def add_language(data, query, confirm=False): print(f"\nFound language: {code} - {name} ({native_name})") if code in data: - print( - f"Warning: '{code}' already exists: " - f"{data[code].get('name')} ({data[code].get('native_name')})" - ) + print(f"Warning: '{code}' already exists: {data[code].get('name')} ({data[code].get('native_name')})") if confirm and input("Add this language? (Y/N): ").lower() != "y": print("Language not added.") @@ -130,9 +121,7 @@ def add_language(data, query, confirm=False): def main(): - parser = argparse.ArgumentParser( - description="Add languages to le_utils languagelookup.json" - ) + parser = argparse.ArgumentParser(description="Add languages to le_utils languagelookup.json") parser.add_argument( "languages", nargs="*", @@ -141,9 +130,7 @@ def main(): args = parser.parse_args() # Get file path - file_path = os.path.join( - os.path.dirname(__file__), "../le_utils/resources/languagelookup.json" - ) + file_path = os.path.join(os.path.dirname(__file__), "../le_utils/resources/languagelookup.json") # Load existing data data = load_json_file(file_path) @@ -161,9 +148,7 @@ def main(): added += 1 else: while True: - query = input( - "\nEnter language name or code to add (or press Enter to finish): " - ) + query = input("\nEnter language name or code to add (or press Enter to finish): ") if not query: break if add_language(data, query, confirm=True): diff --git a/scripts/generate_from_specs.py b/scripts/generate_from_specs.py index 91e158e..c9222f2 100644 --- a/scripts/generate_from_specs.py +++ b/scripts/generate_from_specs.py @@ -2,6 +2,7 @@ generate_from_specs Builds or rebuilds the labels py files assigning ids to the labels """ + from __future__ import unicode_literals import json @@ -13,10 +14,8 @@ from collections import OrderedDict from glob import glob from hashlib import md5 -from uuid import UUID -from uuid import uuid3 - -from pkg_resources import get_distribution +from importlib.metadata import version as get_version +from uuid import UUID, uuid3 try: FileNotFoundError @@ -153,9 +152,7 @@ def read_constants_specs(): constants_outputs[key] = constants_spec else: # assume it's a list and convert to an OrderedDict - constants_outputs[key] = OrderedDict( - [(a.upper(), a) for a in sorted(constants_spec)] - ) + constants_outputs[key] = OrderedDict([(a.upper(), a) for a in sorted(constants_spec)]) return constants_outputs @@ -180,16 +177,7 @@ def read_schema_specs(): for json_schema_def in json_schema.get("definitions", {}).values(): export_name = json_schema_def.get("$exportConstants") if export_name is not None and "enum" in json_schema_def: - constants_outputs.update( - { - snake_to_pascal(export_name): OrderedDict( - [ - (a.upper(), a) - for a in sorted(json_schema_def.get("enum")) - ] - ) - } - ) + constants_outputs.update({snake_to_pascal(export_name): OrderedDict([(a.upper(), a) for a in sorted(json_schema_def.get("enum"))])}) return schema_outputs, constants_outputs @@ -242,9 +230,7 @@ def write_js_file(output_file, name, ordered_output, schema=None): def write_labels_src_files(label_outputs): output_files = [] for label_type, ordered_output in label_outputs.items(): - py_output_file = os.path.join( - py_labels_output_dir, "{}.py".format(pascal_to_snake(label_type)) - ) + py_output_file = os.path.join(py_labels_output_dir, "{}.py".format(pascal_to_snake(label_type))) write_python_file(py_output_file, label_type, ordered_output) output_files.append(py_output_file) @@ -262,9 +248,7 @@ def write_constants_src_files(constants_outputs, schemas): constant_outputs = constants_outputs.get(key, {}) schema = schemas.get(key, None) - py_output_file = os.path.join( - py_output_dir, "{}.py".format(pascal_to_snake(key)) - ) + py_output_file = os.path.join(py_output_dir, "{}.py".format(pascal_to_snake(key))) write_python_file(py_output_file, key, constant_outputs, schema=schema) output_files.append(py_output_file) @@ -274,15 +258,29 @@ def write_constants_src_files(constants_outputs, schemas): return output_files +def pep440_to_npm_semver(version): + """Convert a PEP 440 version to npm-compatible semver. + + Extracts just the base version (X.Y.Z) to ensure the version is + stable across commits and valid npm semver. Dev/local suffixes from + setuptools-scm change with every commit, which would cause the + rebuild-from-specs pre-commit hook to perpetually modify this file. + On tagged releases, setuptools-scm returns the clean version directly. + """ + match = re.match(r"(\d+\.\d+\.\d+)", version) + return match.group(1) if match else version + + def set_package_json_version(): - python_version = get_distribution("le-utils").version + python_version = get_version("le-utils") + npm_version = pep440_to_npm_semver(python_version) package_json = os.path.join(js_output_dir, "package.json") with open(package_json, "r") as f: package = json.load(f) - package["version"] = python_version + package["version"] = npm_version with open(package_json, "w") as f: output = json.dumps(package, indent=2, sort_keys=True) @@ -312,7 +310,6 @@ def set_package_json_version(): output_files += set_package_json_version() - env = os.environ.copy() - env["SKIP"] = "rebuild-from-specs" - - subprocess.call(["pre-commit", "run", "--files"] + output_files, env=env) + py_files = [f for f in output_files if f.endswith(".py")] + subprocess.call(["ruff", "check", "--fix"] + py_files) + subprocess.call(["ruff", "format"] + py_files) diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 8538c32..0000000 --- a/setup.cfg +++ /dev/null @@ -1,16 +0,0 @@ -[metadata] -long_description = file: README.md - -[flake8] -max-line-length = 160 -max-complexity = 10 - -# Ignore non-PEP8-compliant rules so that the Black formatter can be used -ignore = E203,W503,E741 - -[isort] -atomic = true -multi_line_output = 5 -line_length = 160 -indent = ' ' -combine_as_imports = true diff --git a/setup.py b/setup.py deleted file mode 100644 index e12e69e..0000000 --- a/setup.py +++ /dev/null @@ -1,33 +0,0 @@ -import io - -from setuptools import find_packages -from setuptools import setup - -long_description = io.open("README.md", encoding="utf-8").read() - -setup( - name="le-utils", - packages=find_packages(), - version="0.2.16", - description="LE-Utils contains shared constants used in Kolibri, Ricecooker, and Kolibri Studio.", - long_description=long_description, - long_description_content_type="text/markdown", - install_requires=[], - extras_require={}, - license="MIT", - url="https://github.com/learningequality/le-utils", - download_url="https://github.com/learningequality/le-utils/releases", - keywords="le-utils le_utils LE utils kolibri studio ricecooker content curation", - package_data={ - "le_utils": ["resources/*.json"], - }, - classifiers=[ - "Development Status :: 2 - Pre-Alpha", - "Programming Language :: Python", - "License :: OSI Approved :: MIT License", - "Topic :: Utilities", - ], - author="Learning Equality", - author_email="info@learningequality.org", - python_requires=">=3.6, <3.15", -) diff --git a/tests/test_formats.py b/tests/test_formats.py index 1a6568c..91006af 100644 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -8,9 +8,7 @@ def test_file_format_extensions_are_synced(): - formatlookup = json.loads( - pkgutil.get_data("le_utils", "resources/formatlookup.json").decode("utf-8") - ) + formatlookup = json.loads(pkgutil.get_data("le_utils", "resources/formatlookup.json").decode("utf-8")) exts_formatlookup = set(dict(formatlookup).keys()) exts_file_formats = set(dict(file_formats.choices).keys()) diff --git a/tests/test_getlangs.py b/tests/test_getlangs.py index 967045b..88fd15c 100644 --- a/tests/test_getlangs.py +++ b/tests/test_getlangs.py @@ -5,7 +5,6 @@ from le_utils.constants import languages - # getlang ==> Internal representation code lookup ################################################################################ @@ -145,9 +144,7 @@ def test_known_native_names(): def test_unknown_native_language(): lang_obj = languages.getlang_by_native_name("UnknoenNativeLanguage") - assert ( - lang_obj is None - ), "query for natove_name UnknoenNativeLanguage returned non-None" + assert lang_obj is None, "query for natove_name UnknoenNativeLanguage returned non-None" def test_language_native_names_with_modifier_in_bracket(): @@ -206,9 +203,7 @@ def test_african_languages(african_languages_list): lang_obj = languages.getlang_by_native_name(native_name) if lang_obj is None: missing_names.append(native_name) - assert missing_names == [], "Languages with native_names missing: " + str( - missing_names - ) + assert missing_names == [], "Languages with native_names missing: " + str(missing_names) def test_language_text_direction(): diff --git a/tests/test_kinds.py b/tests/test_kinds.py index bb7fc20..2ef3e27 100644 --- a/tests/test_kinds.py +++ b/tests/test_kinds.py @@ -8,9 +8,7 @@ def test_content_kind_extensions_are_synced(): - kindlookup = json.loads( - pkgutil.get_data("le_utils", "resources/kindlookup.json").decode("utf-8") - ) + kindlookup = json.loads(pkgutil.get_data("le_utils", "resources/kindlookup.json").decode("utf-8")) kinds_json = set(dict(kindlookup).keys()) kinds_py = set(dict(content_kinds.choices).keys()) assert kinds_json == kinds_py diff --git a/tests/test_languages.py b/tests/test_languages.py index 02501e6..cee8f08 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -3,13 +3,11 @@ from le_utils.constants import languages - # first_native_name -- split native_name and return first part ################################################################################ def test_first_native_name(): - # basic native name lang_obj = languages.getlang("en") assert lang_obj is not None, "English not found" diff --git a/tests/test_presets.py b/tests/test_presets.py index 7d09c66..ddbe93c 100644 --- a/tests/test_presets.py +++ b/tests/test_presets.py @@ -8,9 +8,7 @@ def test_format_presets_are_synced(): - presetlookup = json.loads( - pkgutil.get_data("le_utils", "resources/presetlookup.json").decode("utf-8") - ) + presetlookup = json.loads(pkgutil.get_data("le_utils", "resources/presetlookup.json").decode("utf-8")) presets_json = set(dict(presetlookup).keys()) presets_python = set(dict(format_presets.choices).keys()) assert presets_json == presets_python diff --git a/tests/test_schemas.py b/tests/test_schemas.py index fd51e91..6697c0a 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -8,11 +8,7 @@ import pytest -from le_utils.constants import completion_criteria -from le_utils.constants import embed_content_request -from le_utils.constants import embed_topics_request -from le_utils.constants import learning_objectives -from le_utils.constants import mastery_criteria +from le_utils.constants import completion_criteria, embed_content_request, embed_topics_request, learning_objectives, mastery_criteria try: # the jsonschema package for python 3.4 is too old, so if not present, we'll just skip @@ -22,9 +18,7 @@ # create a common decorator to skip tests if jsonschema is not available -skip_if_jsonschema_unavailable = pytest.mark.skipif( - jsonschema is None, reason="jsonschema package is unavailable" -) +skip_if_jsonschema_unavailable = pytest.mark.skipif(jsonschema is None, reason="jsonschema package is unavailable") resolver = None @@ -32,9 +26,7 @@ # this is an example of how to include the mastery criteria schema, which is referenced by the # completion criteria schema, in the schema resolver so that it validates resolver = jsonschema.RefResolver.from_schema(mastery_criteria.SCHEMA) - resolver.store.update( - jsonschema.RefResolver.from_schema(completion_criteria.SCHEMA).store - ) + resolver.store.update(jsonschema.RefResolver.from_schema(completion_criteria.SCHEMA).store) def _validate_embed_content_request(data): @@ -58,9 +50,7 @@ def _validate_completion_criteria(data): :param data: Dictionary of data to validate :raises: jsonschema.ValidationError: When invalid """ - jsonschema.validate( - instance=data, schema=completion_criteria.SCHEMA, resolver=resolver - ) + jsonschema.validate(instance=data, schema=completion_criteria.SCHEMA, resolver=resolver) def _load_language_codes(): @@ -86,9 +76,7 @@ def _assert_not_raises(not_expected): @skip_if_jsonschema_unavailable def test_completion_criteria__time_model__valid(): with _assert_not_raises(jsonschema.ValidationError): - _validate_completion_criteria( - {"model": "time", "threshold": 2, "learner_managed": False} - ) + _validate_completion_criteria({"model": "time", "threshold": 2, "learner_managed": False}) _validate_completion_criteria( { "model": "time", @@ -119,9 +107,7 @@ def test_completion_criteria__time_model__invalid(): @skip_if_jsonschema_unavailable def test_completion_criteria__approx_time_model__valid(): with _assert_not_raises(jsonschema.ValidationError): - _validate_completion_criteria( - {"model": "approx_time", "threshold": 2, "learner_managed": False} - ) + _validate_completion_criteria({"model": "approx_time", "threshold": 2, "learner_managed": False}) _validate_completion_criteria( { "model": "approx_time", @@ -152,9 +138,7 @@ def test_completion_criteria__approx_time_model__invalid(): @skip_if_jsonschema_unavailable def test_completion_criteria__pages_model__valid(): with _assert_not_raises(jsonschema.ValidationError): - _validate_completion_criteria( - {"model": "pages", "threshold": 2, "learner_managed": False} - ) + _validate_completion_criteria({"model": "pages", "threshold": 2, "learner_managed": False}) _validate_completion_criteria( { "model": "pages", @@ -245,9 +229,7 @@ def test_completion_criteria__mastery_model__valid(): uuid.uuid4().hex, ], # v4 UUID "version_a_item_ids": [uuid.uuid4().hex], # v4 UUID - "version_b_item_ids": [ - uuid.uuid5(uuid.NAMESPACE_DNS, "test").hex - ], # v5 UUID + "version_b_item_ids": [uuid.uuid5(uuid.NAMESPACE_DNS, "test").hex], # v5 UUID }, }, } @@ -822,21 +804,15 @@ def test_learning_objectives__valid_uuid_v5(): }, ], "assessment_objectives": { - uuid.uuid5(uuid.NAMESPACE_DNS, "test").hex: [ - uuid.uuid5(uuid.NAMESPACE_DNS, "test3").hex - ], + uuid.uuid5(uuid.NAMESPACE_DNS, "test").hex: [uuid.uuid5(uuid.NAMESPACE_DNS, "test3").hex], uuid.uuid5(uuid.NAMESPACE_DNS, "test2").hex: [ uuid.uuid5(uuid.NAMESPACE_DNS, "test4").hex, uuid.uuid5(uuid.NAMESPACE_DNS, "test5").hex, ], }, "lesson_objectives": { - "abcdef1234567890abcdef1234567890": [ - uuid.uuid5(uuid.NAMESPACE_DNS, "test6").hex - ], - "abcdef1234567890abcdef1234567891": [ - uuid.uuid5(uuid.NAMESPACE_DNS, "test7").hex - ], + "abcdef1234567890abcdef1234567890": [uuid.uuid5(uuid.NAMESPACE_DNS, "test6").hex], + "abcdef1234567890abcdef1234567891": [uuid.uuid5(uuid.NAMESPACE_DNS, "test7").hex], }, } ) @@ -851,9 +827,7 @@ def test_learning_objectives__invalid_lo_structure(): {"id": uuid.uuid4().hex}, # Missing text ], "assessment_objectives": {uuid.uuid4().hex: [uuid.uuid4().hex]}, - "lesson_objectives": { - "abcdef1234567890abcdef1234567890": [uuid.uuid4().hex] - }, + "lesson_objectives": {"abcdef1234567890abcdef1234567890": [uuid.uuid4().hex]}, } ) @@ -867,9 +841,7 @@ def test_learning_objectives__invalid_assessment_mapping(): "assessment_objectives": { uuid.uuid4().hex: uuid.uuid4().hex, # Should be an array }, - "lesson_objectives": { - "abcdef1234567890abcdef1234567890": [uuid.uuid4().hex] - }, + "lesson_objectives": {"abcdef1234567890abcdef1234567890": [uuid.uuid4().hex]}, } ) @@ -882,9 +854,7 @@ def test_learning_objectives__invalid_lesson_mapping(): "learning_objectives": [{"id": uuid.uuid4().hex, "text": "LO1"}], "assessment_objectives": {uuid.uuid4().hex: [uuid.uuid4().hex]}, "lesson_objectives": { - "abcdef1234567890abcdef1234567890": str( - uuid.uuid4() - ), # Should be an array + "abcdef1234567890abcdef1234567890": str(uuid.uuid4()), # Should be an array }, } ) @@ -897,9 +867,7 @@ def test_learning_objectives__missing_required_fields(): _validate_learning_objectives( { "assessment_objectives": {uuid.uuid4().hex: [uuid.uuid4().hex]}, - "lesson_objectives": { - "abcdef1234567890abcdef1234567890": [uuid.uuid4().hex] - }, + "lesson_objectives": {"abcdef1234567890abcdef1234567890": [uuid.uuid4().hex]}, } ) @@ -930,9 +898,7 @@ def test_learning_objectives__empty_structures(): { "learning_objectives": [], "assessment_objectives": {uuid.uuid4().hex: [uuid.uuid4().hex]}, - "lesson_objectives": { - "abcdef1234567890abcdef1234567890": [uuid.uuid4().hex] - }, + "lesson_objectives": {"abcdef1234567890abcdef1234567890": [uuid.uuid4().hex]}, } ) @@ -942,9 +908,7 @@ def test_learning_objectives__empty_structures(): { "learning_objectives": [{"id": uuid.uuid4().hex, "text": "LO1"}], "assessment_objectives": {}, - "lesson_objectives": { - "abcdef1234567890abcdef1234567890": [uuid.uuid4().hex] - }, + "lesson_objectives": {"abcdef1234567890abcdef1234567890": [uuid.uuid4().hex]}, } ) @@ -967,9 +931,7 @@ def test_learning_objectives__invalid_uuid_format_in_learning_objectives(): { "learning_objectives": [{"id": "invalid-uuid", "text": "LO1"}], "assessment_objectives": {uuid.uuid4().hex: [uuid.uuid4().hex]}, - "lesson_objectives": { - "abcdef1234567890abcdef1234567890": [uuid.uuid4().hex] - }, + "lesson_objectives": {"abcdef1234567890abcdef1234567890": [uuid.uuid4().hex]}, } ) @@ -982,9 +944,7 @@ def test_learning_objectives__invalid_uuid_format_in_references(): { "learning_objectives": [{"id": uuid.uuid4().hex, "text": "LO1"}], "assessment_objectives": {uuid.uuid4().hex: ["invalid-uuid"]}, - "lesson_objectives": { - "abcdef1234567890abcdef1234567890": [uuid.uuid4().hex] - }, + "lesson_objectives": {"abcdef1234567890abcdef1234567890": [uuid.uuid4().hex]}, } ) @@ -996,12 +956,8 @@ def test_learning_objectives__invalid_question_id_pattern(): _validate_learning_objectives( { "learning_objectives": [{"id": uuid.uuid4().hex, "text": "LO1"}], - "assessment_objectives": { - "invalid-uuid-format": [uuid.uuid4().hex] - }, # Invalid UUID format - "lesson_objectives": { - "abcdef1234567890abcdef1234567890": [uuid.uuid4().hex] - }, + "assessment_objectives": {"invalid-uuid-format": [uuid.uuid4().hex]}, # Invalid UUID format + "lesson_objectives": {"abcdef1234567890abcdef1234567890": [uuid.uuid4().hex]}, } ) @@ -1014,9 +970,7 @@ def test_learning_objectives__invalid_lesson_id_pattern(): { "learning_objectives": [{"id": uuid.uuid4().hex, "text": "LO1"}], "assessment_objectives": {uuid.uuid4().hex: [uuid.uuid4().hex]}, - "lesson_objectives": { - "abcdef1234567890abcdef1234567890:extra": [uuid.uuid4().hex] - }, # Has extra characters + "lesson_objectives": {"abcdef1234567890abcdef1234567890:extra": [uuid.uuid4().hex]}, # Has extra characters } ) @@ -1027,13 +981,9 @@ def test_learning_objectives__invalid_text_patterns(): with pytest.raises(jsonschema.ValidationError): _validate_learning_objectives( { - "learning_objectives": [ - {"id": uuid.uuid4().hex, "text": " "} - ], # Only whitespace + "learning_objectives": [{"id": uuid.uuid4().hex, "text": " "}], # Only whitespace "assessment_objectives": {uuid.uuid4().hex: [uuid.uuid4().hex]}, - "lesson_objectives": { - "abcdef1234567890abcdef1234567890": [uuid.uuid4().hex] - }, + "lesson_objectives": {"abcdef1234567890abcdef1234567890": [uuid.uuid4().hex]}, } ) @@ -1041,12 +991,8 @@ def test_learning_objectives__invalid_text_patterns(): with pytest.raises(jsonschema.ValidationError): _validate_learning_objectives( { - "learning_objectives": [ - {"id": uuid.uuid4().hex, "text": ""} - ], # Empty string + "learning_objectives": [{"id": uuid.uuid4().hex, "text": ""}], # Empty string "assessment_objectives": {uuid.uuid4().hex: [uuid.uuid4().hex]}, - "lesson_objectives": { - "abcdef1234567890abcdef1234567890": [uuid.uuid4().hex] - }, + "lesson_objectives": {"abcdef1234567890abcdef1234567890": [uuid.uuid4().hex]}, } ) diff --git a/tests/test_uuid.py b/tests/test_uuid.py index 59681e9..4eb25ae 100644 --- a/tests/test_uuid.py +++ b/tests/test_uuid.py @@ -9,6 +9,4 @@ def test_namespace_does_not_change(): def test_generate_ecosystem_namespaced_uuid(): - assert generate_ecosystem_namespaced_uuid( - "https://studio.learningequality.org" - ) == uuid.UUID("2d04ed86-aa8c-519f-ae78-a250e03d8482") + assert generate_ecosystem_namespaced_uuid("https://studio.learningequality.org") == uuid.UUID("2d04ed86-aa8c-519f-ae78-a250e03d8482") diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 9700855..0000000 --- a/tox.ini +++ /dev/null @@ -1,20 +0,0 @@ -[tox] -envlist = py{3.6,3.7,3.8,3.9,3.10,3.11,3.12,3.13,3.14} - -[testenv] -basepython = - py3.6: python3.6 - py3.7: python3.7 - py3.8: python3.8 - py3.9: python3.9 - py3.10: python3.10 - py3.11: python3.11 - py3.12: python3.12 - py3.13: python3.13 - py3.14: python3.14 -deps = -r{toxinidir}/requirements-test.txt -setenv = - PYTHONPATH = {toxinidir} -commands = - pip install -U pip - pytest --basetemp={envtmpdir} diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..810e987 --- /dev/null +++ b/uv.lock @@ -0,0 +1,803 @@ +version = 1 +revision = 3 +requires-python = ">=3.6, <3.15" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", + "python_full_version == '3.7.*'", + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] + +[options] +exclude-newer = "2026-03-28T04:42:43.37992949Z" +exclude-newer-span = "P7D" + +[[package]] +name = "atomicwrites" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/c6/53da25344e3e3a9c01095a89f16dbcda021c609ddb42dd6d7c0528236fb2/atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11", size = 14227, upload-time = "2022-07-08T18:31:40.459Z" } + +[[package]] +name = "attrs" +version = "22.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/21/31/3f468da74c7de4fcf9b25591e682856389b3400b4b62f201e65f15ea3e07/attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99", size = 215900, upload-time = "2022-12-21T09:48:51.773Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/6e/6f83bf616d2becdf333a1640f1d463fef3150e2e926b7010cb0f81c95e88/attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", size = 60018, upload-time = "2022-12-21T09:48:49.401Z" }, +] + +[[package]] +name = "attrs" +version = "24.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +dependencies = [ + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678, upload-time = "2024-08-06T14:37:38.364Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001, upload-time = "2024-08-06T14:37:36.958Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "4.8.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "typing-extensions", version = "4.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "zipp", version = "3.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/ed/e65128cc5cb1580f22ee3009d9187ecdfcc43ffb3b581fe854b24e87d8e7/importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668", size = 41979, upload-time = "2021-12-16T14:42:26.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/a1/b153a0a4caf7a7e3f15c2cd56c7702e2cf3d89b1b359d1f1c5e59d68f4ce/importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e", size = 17978, upload-time = "2021-12-16T14:42:25.457Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "zipp", version = "3.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/82/f6e29c8d5c098b6be61460371c2c5591f4a335923639edec43b3830650a4/importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4", size = 53569, upload-time = "2023-06-18T21:44:35.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/94/64287b38c7de4c90683630338cf28f129decbba0a44f0c6db35a873c73c4/importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5", size = 22934, upload-time = "2023-06-18T21:44:33.441Z" }, +] + +[[package]] +name = "iniconfig" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/23/a2/97899f6bd0e873fed3a7e67ae8d3a08b21799430fb4da15cfedf10d6e2c2/iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32", size = 8104, upload-time = "2020-10-14T10:20:18.572Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", size = 4990, upload-time = "2020-10-16T17:37:23.05Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jsonschema" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", version = "22.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "attrs", version = "24.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "attrs", version = "25.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "attrs", version = "26.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "importlib-metadata", version = "4.8.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "pyrsistent", version = "0.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "pyrsistent", version = "0.19.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "pyrsistent", version = "0.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "setuptools", version = "59.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "setuptools", version = "68.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "setuptools", version = "75.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "setuptools", version = "82.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/11/a69e2a3c01b324a77d3a7c0570faa372e8448b666300c4117a516f8b1212/jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a", size = 167226, upload-time = "2019-11-18T12:57:10.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/8f/51e89ce52a085483359217bc72cdbf6e75ee595d5b1d4b5ade40c7e018b8/jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", size = 56305, upload-time = "2019-11-18T12:57:08.454Z" }, +] + +[[package]] +name = "le-utils" +source = { editable = "." } + +[package.dev-dependencies] +dev = [ + { name = "jsonschema" }, + { name = "prek", marker = "python_full_version >= '3.8'" }, + { name = "pytest", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.7'" }, + { name = "ruff", marker = "python_full_version >= '3.8'" }, +] +test = [ + { name = "jsonschema" }, + { name = "pytest", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.7'" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "jsonschema", specifier = "==3.2.0" }, + { name = "prek", marker = "python_full_version >= '3.8'" }, + { name = "pytest", specifier = ">=6.2.5,<8" }, + { name = "ruff", marker = "python_full_version >= '3.8'" }, +] +test = [ + { name = "jsonschema", specifier = "==3.2.0" }, + { name = "pytest", specifier = ">=6.2.5,<8" }, +] + +[[package]] +name = "packaging" +version = "21.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "pyparsing", version = "3.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.6.8'" }, + { name = "pyparsing", version = "3.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.6.8' and python_full_version < '3.7'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/9e/d1a7217f69310c1db8fdf8ab396229f55a699ce34a203691794c5d1cad0c/packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", size = 84848, upload-time = "2021-11-18T00:39:13.586Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522", size = 40750, upload-time = "2021-11-18T00:39:10.932Z" }, +] + +[[package]] +name = "packaging" +version = "24.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/b5/b43a27ac7472e1818c4bafd44430e69605baefe1f34440593e0332ec8b4d/packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9", size = 147882, upload-time = "2024-03-10T09:39:28.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", size = 53488, upload-time = "2024-03-10T09:39:25.947Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pluggy" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "importlib-metadata", version = "4.8.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/16/db2d7de3474b6e37cbb9c008965ee63835bba517e22cdb8c35b5116b5ce1/pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", size = 51510, upload-time = "2021-08-25T16:26:02.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/01/f38e2ff29715251cf25532b9082a1589ab7e4f571ced434f98d0139336dc/pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3", size = 13667, upload-time = "2021-08-25T16:25:59.674Z" }, +] + +[[package]] +name = "pluggy" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +dependencies = [ + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/42/8f2833655a29c4e9cb52ee8a2be04ceac61bcff4a680fb338cbd3d1e322d/pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3", size = 61613, upload-time = "2023-06-21T09:12:28.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/32/4a79112b8b87b21450b066e102d6608907f4c885ed7b04c3fdb085d4d6ae/pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", size = 17695, upload-time = "2023-06-21T09:12:27.397Z" }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prek" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/ee/03e8180e3fda9de25b6480bd15cc2bde40d573868d50648b0e527b35562f/prek-0.3.8.tar.gz", hash = "sha256:434a214256516f187a3ab15f869d950243be66b94ad47987ee4281b69643a2d9", size = 400224, upload-time = "2026-03-23T08:23:35.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/84/40d2ddf362d12c4cd4a25a8c89a862edf87cdfbf1422aa41aac8e315d409/prek-0.3.8-py3-none-linux_armv6l.whl", hash = "sha256:6fb646ada60658fa6dd7771b2e0fb097f005151be222f869dada3eb26d79ed33", size = 5226646, upload-time = "2026-03-23T08:23:18.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/52/7308a033fa43b7e8e188797bd2b3b017c0f0adda70fa7af575b1f43ea888/prek-0.3.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f3d7fdadb15efc19c09953c7a33cf2061a70f367d1e1957358d3ad5cc49d0616", size = 5620104, upload-time = "2026-03-23T08:23:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b1/f106ac000a91511a9cd80169868daf2f5b693480ef5232cec5517a38a512/prek-0.3.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:72728c3295e79ca443f8c1ec037d2a5b914ec73a358f69cf1bc1964511876bf8", size = 5199867, upload-time = "2026-03-23T08:23:38.066Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e9/970713f4b019f69de9844e1bab37b8ddb67558e410916f4eb5869a696165/prek-0.3.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:48efc28f2f53b5b8087efca9daaed91572d62df97d5f24a1c7a087fecb5017de", size = 5441801, upload-time = "2026-03-23T08:23:32.617Z" }, + { url = "https://files.pythonhosted.org/packages/12/a4/7ef44032b181753e19452ec3b09abb3a32607cf6b0a0508f0604becaaf2b/prek-0.3.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6ca9d63bacbc448a5c18e955c78d3ac5176c3a17c3baacdd949b1a623e08a36", size = 5155107, upload-time = "2026-03-23T08:23:31.021Z" }, + { url = "https://files.pythonhosted.org/packages/bd/77/4d9c8985dbba84149760785dfe07093ea1e29d710257dfb7c89615e2234c/prek-0.3.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1000f7029696b4fe712fb1fefd4c55b9c4de72b65509c8e50296370a06f9dc3f", size = 5566541, upload-time = "2026-03-23T08:23:45.694Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1a/81e6769ac1f7f8346d09ce2ab0b47cf06466acd9ff72e87e5d1f0d98cd32/prek-0.3.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ff0bed0e2c1286522987d982168a86cbbd0d069d840506a46c9fda983515517", size = 6552991, upload-time = "2026-03-23T08:23:21.958Z" }, + { url = "https://files.pythonhosted.org/packages/6f/fa/ce2df0dd2dc75a9437a52463239d0782998943d7b04e191fb89b83016c34/prek-0.3.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fb087ac0ffda3ac65bbbae9a38326a7fd27ee007bb4a94323ce1eb539d8bbec", size = 5832972, upload-time = "2026-03-23T08:23:20.258Z" }, + { url = "https://files.pythonhosted.org/packages/18/6b/9d4269df9073216d296244595a21c253b6475dfc9076c0bd2906be7a436c/prek-0.3.8-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:2e1e5e206ff7b31bd079cce525daddc96cd6bc544d20dc128921ad92f7a4c85d", size = 5448371, upload-time = "2026-03-23T08:23:41.835Z" }, + { url = "https://files.pythonhosted.org/packages/60/1d/1e4d8a78abefa5b9d086e5a9f1638a74b5e540eec8a648d9946707701f29/prek-0.3.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:dcea3fe23832a4481bccb7c45f55650cb233be7c805602e788bb7dba60f2d861", size = 5270546, upload-time = "2026-03-23T08:23:24.231Z" }, + { url = "https://files.pythonhosted.org/packages/77/07/34f36551a6319ae36e272bea63a42f59d41d2d47ab0d5fb00eb7b4e88e87/prek-0.3.8-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:4d25e647e9682f6818ab5c31e7a4b842993c14782a6ffcd128d22b784e0d677f", size = 5124032, upload-time = "2026-03-23T08:23:26.368Z" }, + { url = "https://files.pythonhosted.org/packages/e3/01/6d544009bb655e709993411796af77339f439526db4f3b3509c583ad8eb9/prek-0.3.8-py3-none-musllinux_1_1_i686.whl", hash = "sha256:de528b82935e33074815acff3c7c86026754d1212136295bc88fe9c43b4231d5", size = 5432245, upload-time = "2026-03-23T08:23:47.877Z" }, + { url = "https://files.pythonhosted.org/packages/54/96/1237ee269e9bfa283ffadbcba1f401f48a47aed2b2563eb1002740d6079d/prek-0.3.8-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:6d660f1c25a126e6d9f682fe61449441226514f412a4469f5d71f8f8cad56db2", size = 5950550, upload-time = "2026-03-23T08:23:43.8Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6b/a574411459049bc691047c9912f375deda10c44a707b6ce98df2b658f0b3/prek-0.3.8-py3-none-win32.whl", hash = "sha256:b0c291c577615d9f8450421dff0b32bfd77a6b0d223ee4115a1f820cb636fdf1", size = 4949501, upload-time = "2026-03-23T08:23:16.338Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b4/46b59fe49f635acd9f6530778ce577f9d8b49452835726a5311ffc902c67/prek-0.3.8-py3-none-win_amd64.whl", hash = "sha256:bc147fdbdd4ec33fc7a987b893ecb69b1413ac100d95c9889a70f3fd58c73d06", size = 5346551, upload-time = "2026-03-23T08:23:34.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/05/9cca1708bb8c65264124eb4b04251e0f65ce5bfc707080bb6b492d5a0df7/prek-0.3.8-py3-none-win_arm64.whl", hash = "sha256:a2614647aeafa817a5802ccb9561e92eedc20dcf840639a1b00826e2c2442515", size = 5190872, upload-time = "2026-03-23T08:23:29.463Z" }, +] + +[[package]] +name = "py" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796, upload-time = "2021-11-04T17:17:01.377Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708, upload-time = "2021-11-04T17:17:00.152Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.0.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/60/9bed18f43275b34198eb9720d4c1238c68b3755620d20df0afd89424d32b/pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", size = 884709, upload-time = "2022-01-21T05:41:34.625Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/c1/23fd82ad3121656b585351aba6c19761926bb0db2ebed9e4ff09a43a3fcc/pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484", size = 98049, upload-time = "2022-01-21T05:41:33.032Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", +] +sdist = { url = "https://files.pythonhosted.org/packages/83/08/13f3bce01b2061f2bbd582c9df82723de943784cf719a35ac886c652043a/pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032", size = 900231, upload-time = "2024-08-25T15:00:47.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/0c/0e3c05b1c87bb6a1c76d281b0f35e78d2d80ac91b5f8f524cebf77f51049/pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c", size = 104100, upload-time = "2024-08-25T15:00:45.361Z" }, +] + +[[package]] +name = "pyrsistent" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/d7/0fa558c4fb00f15aabc6d42d365fcca7a15fcc1091cd0f5784a14f390b7f/pyrsistent-0.18.0.tar.gz", hash = "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b", size = 104215, upload-time = "2021-06-28T19:33:39.282Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/8c/0d2de0f77b9c70bffac64a827c003888df3c400d40df3769cc75dcc86533/pyrsistent-0.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72", size = 68822, upload-time = "2021-06-28T19:33:04.899Z" }, + { url = "https://files.pythonhosted.org/packages/50/eb/5d286a7673ece30c939c4cacd8ced8268d13eacf207d2a47c8dc9622cbbb/pyrsistent-0.18.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d", size = 111818, upload-time = "2021-06-28T19:33:07.219Z" }, + { url = "https://files.pythonhosted.org/packages/6c/19/1af501f6f388a40ede6d0185ba481bdb18ffc99deab0dd0d092b173bc0f4/pyrsistent-0.18.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2", size = 117856, upload-time = "2021-06-28T19:33:09.049Z" }, + { url = "https://files.pythonhosted.org/packages/36/96/59de666cb6ffd676734e586e3fbcc50ebf72ac3a0bc51c95daabb5e21727/pyrsistent-0.18.0-cp36-cp36m-win32.whl", hash = "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1", size = 59917, upload-time = "2021-06-28T19:33:11.044Z" }, + { url = "https://files.pythonhosted.org/packages/90/d4/abeef6b81e77d0a6dc55153f51b40f62639b4160c33a4e2945828a24c1d7/pyrsistent-0.18.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7", size = 62735, upload-time = "2021-06-28T19:33:12.185Z" }, + { url = "https://files.pythonhosted.org/packages/d4/63/5ef75e0e19154787bee90b0bf1567237e67c56e1d6b8ea117829a3f7c6a9/pyrsistent-0.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396", size = 68813, upload-time = "2021-06-28T19:33:13.168Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/38e250b4908e90167d66ca22b22728c12a7cb45678850cc80e0ba602318f/pyrsistent-0.18.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710", size = 112973, upload-time = "2021-06-28T19:33:15.094Z" }, + { url = "https://files.pythonhosted.org/packages/b6/31/6e33022d5056ac64972f2d1411ba04261668e5d4e90b23c115f2e81283bc/pyrsistent-0.18.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35", size = 119004, upload-time = "2021-06-28T19:33:17.32Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f3/cf976c45323aa74f60632bb7b7d19799ece4aead7ed9b8c6dd6266e68a00/pyrsistent-0.18.0-cp37-cp37m-win32.whl", hash = "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f", size = 59933, upload-time = "2021-06-28T19:33:19.533Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/1bea09b76c845863aa10e7ef364d97101041b21952033f35f7444926093a/pyrsistent-0.18.0-cp37-cp37m-win_amd64.whl", hash = "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2", size = 62766, upload-time = "2021-06-28T19:33:20.498Z" }, + { url = "https://files.pythonhosted.org/packages/25/c8/fc1cf625433aae3305cc492b035ac41a1be5ed4b01e2aac46b8d0dd79847/pyrsistent-0.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427", size = 68986, upload-time = "2021-06-28T19:33:21.541Z" }, + { url = "https://files.pythonhosted.org/packages/7b/25/c8291eb3cfd066f8d4a534604de720b1be7517182056dabaecc63384e8e1/pyrsistent-0.18.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef", size = 112218, upload-time = "2021-06-28T19:33:23.203Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5b/1514e921fd749e53ecfeb7cf65ba7d8946123f25de18bf7b7f4ad17226cc/pyrsistent-0.18.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c", size = 118313, upload-time = "2021-06-28T19:33:25.299Z" }, + { url = "https://files.pythonhosted.org/packages/2a/af/947fd0c22b784742054d115d573d2f2f2d23f4580f7af3e10a3ed88bf3b4/pyrsistent-0.18.0-cp38-cp38-win32.whl", hash = "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78", size = 60009, upload-time = "2021-06-28T19:33:27.95Z" }, + { url = "https://files.pythonhosted.org/packages/70/fc/876c4d4853639590d70513134a54e5ca7264b73a0f9321a60fa53b8d47d0/pyrsistent-0.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b", size = 62835, upload-time = "2021-06-28T19:33:29.089Z" }, + { url = "https://files.pythonhosted.org/packages/82/52/67c7ebce1778978cc333e08dd5e375e0031e004e21524af772b6ad60fd74/pyrsistent-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4", size = 68995, upload-time = "2021-06-28T19:33:30.462Z" }, + { url = "https://files.pythonhosted.org/packages/cd/04/d6982b2a1b191b9861e572a15cdb4912804a89f6a7aca762eb9f3223ca25/pyrsistent-0.18.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680", size = 111088, upload-time = "2021-06-28T19:33:32.398Z" }, + { url = "https://files.pythonhosted.org/packages/89/32/51fbef50787c6a430ba0a1bf2605a204c633b540ad18692062cd5363c001/pyrsistent-0.18.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426", size = 117140, upload-time = "2021-06-28T19:33:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ad/c830a6db376266cf91f60ee9ced7408fe90914ae889468976020cdd404b3/pyrsistent-0.18.0-cp39-cp39-win32.whl", hash = "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b", size = 59965, upload-time = "2021-06-28T19:33:36.399Z" }, + { url = "https://files.pythonhosted.org/packages/55/99/5c5cfb1ca079a910efcbb35b8ab3a4ddb50ddf7527dcfcae362eca7fc740/pyrsistent-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea", size = 62748, upload-time = "2021-06-28T19:33:37.849Z" }, +] + +[[package]] +name = "pyrsistent" +version = "0.19.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/90/445a7dbd275c654c268f47fa9452152709134f61f09605cf776407055a89/pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440", size = 102640, upload-time = "2022-12-29T08:00:25.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/7b/7d032130a6838b179b46dff1ee88909c11d518a10ec9bc70c4b72c7c2f80/pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a", size = 82517, upload-time = "2022-12-29T07:59:40.52Z" }, + { url = "https://files.pythonhosted.org/packages/40/04/f1d7813d4cdb62ed58e75b53e2ef481b47081ab5ad2a104cd284fa507042/pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64", size = 117238, upload-time = "2022-12-29T07:59:42.838Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/1e300772f5c24921a81fc1c8b3de8a06a199c4ebb523d7c5a85f4e74a32e/pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf", size = 113755, upload-time = "2022-12-29T07:59:44.417Z" }, + { url = "https://files.pythonhosted.org/packages/57/3e/50aa661939ba1bfc2cc78817ecb37ecb55aef9eda55a193f8da381a8b7a1/pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a", size = 60178, upload-time = "2022-12-29T07:59:46.377Z" }, + { url = "https://files.pythonhosted.org/packages/87/72/e5b2347f136d14f09c8260a2e3a528be94e536d97e6635cc9f22cff2d88c/pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da", size = 62716, upload-time = "2022-12-29T07:59:47.818Z" }, + { url = "https://files.pythonhosted.org/packages/b1/46/3f9cfa75c46b8a55d3a235456bc129a26431a65e4922fc9af66aa4e2db7e/pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9", size = 82552, upload-time = "2022-12-29T07:59:49.121Z" }, + { url = "https://files.pythonhosted.org/packages/64/bd/b108e1a288a63871be1cf062176dcd5be922c748f843f316440104a45df3/pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393", size = 119532, upload-time = "2022-12-29T07:59:51.376Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/fda71652a6baa0147891296a99b4145572538417609c164450beebcf8ebc/pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19", size = 116166, upload-time = "2022-12-29T07:59:53.666Z" }, + { url = "https://files.pythonhosted.org/packages/de/9e/10c5bf794eec650a3aab1b4fb1f6824f53011d111ddfdce1459dc357408a/pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3", size = 60181, upload-time = "2022-12-29T07:59:56.168Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ea/055a9c1884be7c77dd68d9a7891e7a39c776c86946aa4630f8f9f8e48169/pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8", size = 62714, upload-time = "2022-12-29T07:59:57.391Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c2/994b3e91f22b040fefbb3058d8622e3b45ab78dd1256599575bf36319b6d/pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28", size = 69827, upload-time = "2022-12-29T07:59:58.579Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c0/5ba658ab88966a5a709e17739d1da02615b95e8210d52041d147f11da5da/pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf", size = 118070, upload-time = "2022-12-29T08:00:00.951Z" }, + { url = "https://files.pythonhosted.org/packages/86/0e/33b4cde936d247024c26772dae0a7c93d650d8ec7ee1824d2752d3d8883c/pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9", size = 114715, upload-time = "2022-12-29T08:00:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/cf855eb8b1dc98f20e3223c7262068918f22f5ad452a99588cf2e5e70e82/pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1", size = 60180, upload-time = "2022-12-29T08:00:04.428Z" }, + { url = "https://files.pythonhosted.org/packages/d8/95/374840c28274b2d660a49c81aee980543953c9c13bcfc9c8c22fb7737919/pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b", size = 62612, upload-time = "2022-12-29T08:00:05.439Z" }, + { url = "https://files.pythonhosted.org/packages/59/4b/b6ea0f5c564c40f2c9d05ad3dbe3b8db6a6f1e7153e49eee29674c3c3bbe/pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8", size = 82545, upload-time = "2022-12-29T08:00:07.201Z" }, + { url = "https://files.pythonhosted.org/packages/07/d2/0e72806d668c001d13885e8d7c78fefa5a649c34ad9d77b90eb472096ae7/pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a", size = 120978, upload-time = "2022-12-29T08:00:09.224Z" }, + { url = "https://files.pythonhosted.org/packages/82/5e/037a808341e4464c702eb45e741c69292516d0ac00e64080269a2e98d12d/pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c", size = 117571, upload-time = "2022-12-29T08:00:11.682Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/f8e546cbd5aeecce04a5e9d03c32f329c6b97a5514868c824bbe111cd697/pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c", size = 60246, upload-time = "2022-12-29T08:00:13.279Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8d/bbce2d857ecdefb7170a8a37ade1de0f060052236c07693856ac23f3b1ee/pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7", size = 62685, upload-time = "2022-12-29T08:00:14.549Z" }, + { url = "https://files.pythonhosted.org/packages/d5/bf/6ed2d861e3e94c5e92dbb1399eef672fb6add6e824d8c0f4b55d9cd9e733/pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc", size = 82518, upload-time = "2022-12-29T08:00:15.636Z" }, + { url = "https://files.pythonhosted.org/packages/af/3e/7c94e58ade258179c2e13fb254f040830e97654d76dee8288200d30d575d/pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2", size = 117026, upload-time = "2022-12-29T08:00:17.475Z" }, + { url = "https://files.pythonhosted.org/packages/f4/43/183384edb4d2788374aa7663b82ace4afe4a0c1fbfee064875eb40ada95b/pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3", size = 113581, upload-time = "2022-12-29T08:00:19.098Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e6/43c7f666703506f8d5c5d2c7bc223346c6fa0e0fe392074c2b5788a577f8/pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2", size = 60176, upload-time = "2022-12-29T08:00:20.795Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c7/185e37df78c1e34c14961cbd7c89945e27825b5a41bf455e2df2dd46e18e/pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98", size = 62710, upload-time = "2022-12-29T08:00:22.09Z" }, + { url = "https://files.pythonhosted.org/packages/64/de/375aa14daaee107f987da76ca32f7a907fea00fa8b8afb67dc09bec0de91/pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64", size = 57505, upload-time = "2022-12-29T08:00:23.284Z" }, +] + +[[package]] +name = "pyrsistent" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/3a/5031723c09068e9c8c2f0bc25c3a9245f2b1d1aea8396c787a408f2b95ca/pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4", size = 103642, upload-time = "2023-10-25T21:06:56.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/19/c343b14061907b629b765444b6436b160e2bd4184d17d4804bbe6381f6be/pyrsistent-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c3aba3e01235221e5b229a6c05f585f344734bd1ad42a8ac51493d74722bbce", size = 83416, upload-time = "2023-10-25T21:06:04.579Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4f/8342079ea331031ef9ed57edd312a9ad283bcc8adfaf268931ae356a09a6/pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1beb78af5423b879edaf23c5591ff292cf7c33979734c99aa66d5914ead880f", size = 118021, upload-time = "2023-10-25T21:06:06.953Z" }, + { url = "https://files.pythonhosted.org/packages/d7/b7/64a125c488243965b7c5118352e47c6f89df95b4ac306d31cee409153d57/pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cc459636983764e692b9eba7144cdd54fdec23ccdb1e8ba392a63666c60c34", size = 117747, upload-time = "2023-10-25T21:06:08.5Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/43c67bd5f80df9e7583042398d12113263ec57f27c0607abe9d78395d18f/pyrsistent-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5ac696f02b3fc01a710427585c855f65cd9c640e14f52abe52020722bb4906b", size = 114524, upload-time = "2023-10-25T21:06:10.728Z" }, + { url = "https://files.pythonhosted.org/packages/8a/98/b382a87e89ca839106d874f7bf78d226b3eedb26735eb6f751f1a3375f21/pyrsistent-0.20.0-cp310-cp310-win32.whl", hash = "sha256:0724c506cd8b63c69c7f883cc233aac948c1ea946ea95996ad8b1380c25e1d3f", size = 60780, upload-time = "2023-10-25T21:06:12.14Z" }, + { url = "https://files.pythonhosted.org/packages/37/8a/23e2193f7adea6901262e3cf39c7fe18ac0c446176c0ff0e19aeb2e9681e/pyrsistent-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:8441cf9616d642c475684d6cf2520dd24812e996ba9af15e606df5f6fd9d04a7", size = 63310, upload-time = "2023-10-25T21:06:13.598Z" }, + { url = "https://files.pythonhosted.org/packages/df/63/7544dc7d0953294882a5c587fb1b10a26e0c23d9b92281a14c2514bac1f7/pyrsistent-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f3b1bcaa1f0629c978b355a7c37acd58907390149b7311b5db1b37648eb6958", size = 83481, upload-time = "2023-10-25T21:06:15.238Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a0/49249bc14d71b1bf2ffe89703acfa86f2017c25cfdabcaea532b8c8a5810/pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdd7ef1ea7a491ae70d826b6cc64868de09a1d5ff9ef8d574250d0940e275b8", size = 120222, upload-time = "2023-10-25T21:06:17.144Z" }, + { url = "https://files.pythonhosted.org/packages/a1/94/9808e8c9271424120289b9028a657da336ad7e43da0647f62e4f6011d19b/pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae40a9e3ce178415040a0383f00e8d68b569e97f31928a3a8ad37e3fde6df6a", size = 120002, upload-time = "2023-10-25T21:06:18.727Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f6/9ecfb78b2fc8e2540546db0fe19df1fae0f56664a5958c21ff8861b0f8da/pyrsistent-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6288b3fa6622ad8a91e6eb759cfc48ff3089e7c17fb1d4c59a919769314af224", size = 116850, upload-time = "2023-10-25T21:06:20.424Z" }, + { url = "https://files.pythonhosted.org/packages/83/c8/e6d28bc27a0719f8eaae660357df9757d6e9ca9be2691595721de9e8adfc/pyrsistent-0.20.0-cp311-cp311-win32.whl", hash = "sha256:7d29c23bdf6e5438c755b941cef867ec2a4a172ceb9f50553b6ed70d50dfd656", size = 60775, upload-time = "2023-10-25T21:06:21.815Z" }, + { url = "https://files.pythonhosted.org/packages/98/87/c6ef52ff30388f357922d08de012abdd3dc61e09311d88967bdae23ab657/pyrsistent-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:59a89bccd615551391f3237e00006a26bcf98a4d18623a19909a2c48b8e986ee", size = 63306, upload-time = "2023-10-25T21:06:22.874Z" }, + { url = "https://files.pythonhosted.org/packages/15/ee/ff2ed52032ac1ce2e7ba19e79bd5b05d152ebfb77956cf08fcd6e8d760ea/pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e", size = 83537, upload-time = "2023-10-25T21:06:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/80/f1/338d0050b24c3132bcfc79b68c3a5f54bce3d213ecef74d37e988b971d8a/pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e", size = 122615, upload-time = "2023-10-25T21:06:25.815Z" }, + { url = "https://files.pythonhosted.org/packages/07/3a/e56d6431b713518094fae6ff833a04a6f49ad0fbe25fb7c0dc7408e19d20/pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3", size = 122335, upload-time = "2023-10-25T21:06:28.631Z" }, + { url = "https://files.pythonhosted.org/packages/4a/bb/5f40a4d5e985a43b43f607250e766cdec28904682c3505eb0bd343a4b7db/pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d", size = 118510, upload-time = "2023-10-25T21:06:30.718Z" }, + { url = "https://files.pythonhosted.org/packages/1c/13/e6a22f40f5800af116c02c28e29f15c06aa41cb2036f6a64ab124647f28b/pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174", size = 60865, upload-time = "2023-10-25T21:06:32.742Z" }, + { url = "https://files.pythonhosted.org/packages/75/ef/2fa3b55023ec07c22682c957808f9a41836da4cd006b5f55ec76bf0fbfa6/pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d", size = 63239, upload-time = "2023-10-25T21:06:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/a5/24/3293a2b2bc4b4d645f2f6743e97b329c18dd9d8177f80e52d2b7911bac0f/pyrsistent-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:79ed12ba79935adaac1664fd7e0e585a22caa539dfc9b7c7c6d5ebf91fb89054", size = 83450, upload-time = "2023-10-25T21:06:35.707Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ea/5438a78ba00f2a9cdc6836dcdcd8631b9d802b2bd57d5a61ed9d9ad6f24d/pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f920385a11207dc372a028b3f1e1038bb244b3ec38d448e6d8e43c6b3ba20e98", size = 121792, upload-time = "2023-10-25T21:06:37.22Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ff/93dea1abc3e2d44cee0f62974a1f133fc5a4c719c0978148726bd4957b52/pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5c2d012671b7391803263419e31b5c7c21e7c95c8760d7fc35602353dee714", size = 121754, upload-time = "2023-10-25T21:06:38.821Z" }, + { url = "https://files.pythonhosted.org/packages/93/29/93ad2089a3317b00c9f5d863a532339aa44dcd2cd5f8d73c569ef2c9cddb/pyrsistent-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef3992833fbd686ee783590639f4b8343a57f1f75de8633749d984dc0eb16c86", size = 118326, upload-time = "2023-10-25T21:06:40.473Z" }, + { url = "https://files.pythonhosted.org/packages/60/c8/6ca4e647512d27b8a9ffe0daf75e284d1cb770c073d845d5893808a6951e/pyrsistent-0.20.0-cp38-cp38-win32.whl", hash = "sha256:881bbea27bbd32d37eb24dd320a5e745a2a5b092a17f6debc1349252fac85423", size = 60841, upload-time = "2023-10-25T21:06:42.756Z" }, + { url = "https://files.pythonhosted.org/packages/09/6a/6a31c1bbffd4880a8825cea2572e8b3082681215464ebec9404c0b74ab4c/pyrsistent-0.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:6d270ec9dd33cdb13f4d62c95c1a5a50e6b7cdd86302b494217137f760495b9d", size = 63281, upload-time = "2023-10-25T21:06:44.445Z" }, + { url = "https://files.pythonhosted.org/packages/18/0c/289126299fcebf54fd01d385fb5176c328fef2c4233139c23dd48346e992/pyrsistent-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca52d1ceae015859d16aded12584c59eb3825f7b50c6cfd621d4231a6cc624ce", size = 83379, upload-time = "2023-10-25T21:06:45.585Z" }, + { url = "https://files.pythonhosted.org/packages/4e/45/62639d53ac09eaafc00f2e5845565e70d3eddb2d296337a77637186ca03e/pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b318ca24db0f0518630e8b6f3831e9cba78f099ed5c1d65ffe3e023003043ba0", size = 117740, upload-time = "2023-10-25T21:06:46.918Z" }, + { url = "https://files.pythonhosted.org/packages/ab/12/24b9a6ef7b991b6722756e0aa169a39463af2b8ed0fb526f0a00aae34ea4/pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed2c3216a605dc9a6ea50c7e84c82906e3684c4e80d2908208f662a6cbf9022", size = 117457, upload-time = "2023-10-25T21:06:48.911Z" }, + { url = "https://files.pythonhosted.org/packages/19/3c/ab06510f86bc0934b77ade41948924ff1f33dcd3433f32feca2028218837/pyrsistent-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e14c95c16211d166f59c6611533d0dacce2e25de0f76e4c140fde250997b3ca", size = 114280, upload-time = "2023-10-25T21:06:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b1/1275bbfb929854d20e72aa2bbfb50ea3b1d7d41a95848b353691875e2817/pyrsistent-0.20.0-cp39-cp39-win32.whl", hash = "sha256:f058a615031eea4ef94ead6456f5ec2026c19fb5bd6bfe86e9665c4158cf802f", size = 60764, upload-time = "2023-10-25T21:06:52.093Z" }, + { url = "https://files.pythonhosted.org/packages/28/77/0d7af973c0e3b1b83d8b45943601f77f85b943007e3a4d8744f7102c652b/pyrsistent-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:58b8f6366e152092194ae68fefe18b9f0b4f89227dfd86a07770c3d86097aebf", size = 63289, upload-time = "2023-10-25T21:06:53.221Z" }, + { url = "https://files.pythonhosted.org/packages/23/88/0acd180010aaed4987c85700b7cc17f9505f3edb4e5873e4dc67f613e338/pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b", size = 58106, upload-time = "2023-10-25T21:06:54.387Z" }, +] + +[[package]] +name = "pytest" +version = "7.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "atomicwrites", marker = "python_full_version < '3.7' and sys_platform == 'win32'" }, + { name = "attrs", version = "22.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "colorama", marker = "python_full_version < '3.7' and sys_platform == 'win32'" }, + { name = "importlib-metadata", version = "4.8.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "iniconfig", version = "1.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "packaging", version = "21.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "pluggy", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "py", marker = "python_full_version < '3.7'" }, + { name = "tomli", version = "1.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/2c/a67ad48759051c7abf82ce182a4e6d766de371b183182d2dde03089e8dfb/pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171", size = 1249154, upload-time = "2022-02-11T18:47:58.543Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/93/c7c0bd1e932b287fb948eb9ce5a3d6307c9fc619db1e199f8c8bc5dad95f/pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db", size = 296985, upload-time = "2022-02-11T18:47:56.328Z" }, +] + +[[package]] +name = "pytest" +version = "7.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", + "python_full_version == '3.7.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.7' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version >= '3.7' and python_full_version < '3.11'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "iniconfig", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", version = "24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "packaging", version = "26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "pluggy", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "tomli", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "tomli", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116, upload-time = "2023-12-31T12:00:18.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287, upload-time = "2023-12-31T12:00:13.963Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, + { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, + { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, + { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, + { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, +] + +[[package]] +name = "setuptools" +version = "59.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/fa/5ec0fa9095c9b72cb1c31a8175c4c6745bf5927d1045d7a70df35d54944f/setuptools-59.6.0.tar.gz", hash = "sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373", size = 2281973, upload-time = "2021-12-12T20:46:30.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/3a/88b210db68e56854d0bcf4b38e165e03be377e13907746f825790f3df5bf/setuptools-59.6.0-py3-none-any.whl", hash = "sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e", size = 952597, upload-time = "2021-12-12T20:46:28.631Z" }, +] + +[[package]] +name = "setuptools" +version = "68.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/98/5f896af066c128669229ff1aa81553ac14cfb3e5e74b6b44594132b8540e/setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235", size = 2194111, upload-time = "2023-06-19T15:53:05.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/42/be1c7bbdd83e1bfb160c94b9cafd8e25efc7400346cf7ccdbdb452c467fa/setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f", size = 804037, upload-time = "2023-06-19T15:53:03.089Z" }, +] + +[[package]] +name = "setuptools" +version = "75.3.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/93/d2622cbf262418995140dfc1ddb890badd6322893fa122302577c82b9617/setuptools-75.3.4.tar.gz", hash = "sha256:b4ea3f76e1633c4d2d422a5d68ab35fd35402ad71e6acaa5d7e5956eb47e8887", size = 1354595, upload-time = "2026-02-08T14:12:34.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/b1/961ba076c7d3732e3a97e6681c55ef647afb795fe8bfcd27becec8a762ce/setuptools-75.3.4-py3-none-any.whl", hash = "sha256:2dd50a7f42dddfa1d02a36f275dbe716f38ed250224f609d35fb60a09593d93e", size = 1251633, upload-time = "2026-02-08T14:12:32.364Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "tomli" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/2e/d0a8276b0cf9b9e34fd0660c330acc59656f53bb2209adc75af863a3582d/tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f", size = 15094, upload-time = "2021-12-13T22:25:06.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e4/74f9440db36734d7ba83c574c1e7024009ce849208a41f90e94a134dc6d1/tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c", size = 12122, upload-time = "2021-12-13T22:25:05.02Z" }, +] + +[[package]] +name = "tomli" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164, upload-time = "2022-02-08T10:54:04.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757, upload-time = "2022-02-08T10:54:02.017Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/5a/8b5fbb891ef3f81fc923bf3cb4a578c0abf9471eb50ce0f51c74212182ab/typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", size = 26694, upload-time = "2022-02-14T03:19:57.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/6b/44f7f8f1e110027cf88956b59f2fad776cca7e1704396d043f89effd3a0e/typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2", size = 26844, upload-time = "2022-02-14T03:19:55.773Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2", size = 72876, upload-time = "2023-07-02T14:20:55.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", size = 33232, upload-time = "2023-07-02T14:20:53.275Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "zipp" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/02/bf/0d03dbdedb83afec081fefe86cae3a2447250ef1a81ac601a9a56e785401/zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832", size = 13047, upload-time = "2021-09-29T15:34:01.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/df/d4a4974a3e3957fd1c1fa3082366d7fff6e428ddb55f074bf64876f8e8ad/zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc", size = 5313, upload-time = "2021-09-29T15:34:00.831Z" }, +] + +[[package]] +name = "zipp" +version = "3.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/00/27/f0ac6b846684cecce1ee93d32450c45ab607f65c2e0255f0092032d91f07/zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", size = 18454, upload-time = "2023-02-25T02:17:22.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556", size = 6758, upload-time = "2023-02-25T02:17:20.807Z" }, +]