tomlrange 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tomlrange-0.2.2/.github/dependabot.yml +22 -0
- tomlrange-0.2.2/.github/workflows/bump-version.yml +174 -0
- tomlrange-0.2.2/.github/workflows/ci.yml +26 -0
- tomlrange-0.2.2/.github/workflows/cut-release.yml +115 -0
- {tomlrange-0.2.0 → tomlrange-0.2.2}/PKG-INFO +10 -3
- {tomlrange-0.2.0 → tomlrange-0.2.2}/README.md +9 -2
- {tomlrange-0.2.0 → tomlrange-0.2.2}/pyproject.toml +5 -1
- {tomlrange-0.2.0 → tomlrange-0.2.2}/src/tomlrange/__init__.py +7 -1
- {tomlrange-0.2.0 → tomlrange-0.2.2}/src/tomlrange/bound.py +32 -12
- {tomlrange-0.2.0 → tomlrange-0.2.2}/src/tomlrange/domain.py +12 -12
- tomlrange-0.2.2/src/tomlrange/py.typed +0 -0
- {tomlrange-0.2.0 → tomlrange-0.2.2}/tests/test_tomlrange.py +102 -0
- tomlrange-0.2.2/uv.lock +233 -0
- tomlrange-0.2.0/src/tomlrange/py.typed +0 -1
- {tomlrange-0.2.0 → tomlrange-0.2.2}/.github/workflows/publish.yml +0 -0
- {tomlrange-0.2.0 → tomlrange-0.2.2}/.gitignore +0 -0
- {tomlrange-0.2.0 → tomlrange-0.2.2}/.pre-commit-config.yaml +0 -0
- {tomlrange-0.2.0 → tomlrange-0.2.2}/LICENSE +0 -0
- {tomlrange-0.2.0 → tomlrange-0.2.2}/src/tomlrange/error.py +0 -0
- {tomlrange-0.2.0 → tomlrange-0.2.2}/src/tomlrange/paths.py +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Dependabot: GitHub Actions and uv (pyproject.toml + uv.lock)
|
|
2
|
+
version: 2
|
|
3
|
+
updates:
|
|
4
|
+
- package-ecosystem: github-actions
|
|
5
|
+
directory: /
|
|
6
|
+
schedule:
|
|
7
|
+
interval: weekly
|
|
8
|
+
day: monday
|
|
9
|
+
groups:
|
|
10
|
+
github-actions:
|
|
11
|
+
patterns:
|
|
12
|
+
- "*"
|
|
13
|
+
|
|
14
|
+
- package-ecosystem: uv
|
|
15
|
+
directory: /
|
|
16
|
+
schedule:
|
|
17
|
+
interval: weekly
|
|
18
|
+
day: monday
|
|
19
|
+
groups:
|
|
20
|
+
python:
|
|
21
|
+
patterns:
|
|
22
|
+
- "*"
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
name: Bump version
|
|
2
|
+
run-name: Bump ${{ inputs.bump }} (${{ inputs.publish && 'publish' || 'draft' }})
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
inputs:
|
|
7
|
+
bump:
|
|
8
|
+
description: uv version --bump (one per dispatch)
|
|
9
|
+
required: true
|
|
10
|
+
type: choice
|
|
11
|
+
options:
|
|
12
|
+
- patch
|
|
13
|
+
- minor
|
|
14
|
+
- major
|
|
15
|
+
- rc
|
|
16
|
+
- beta
|
|
17
|
+
- alpha
|
|
18
|
+
- stable
|
|
19
|
+
- post
|
|
20
|
+
- dev
|
|
21
|
+
default: patch
|
|
22
|
+
publish:
|
|
23
|
+
description: Published Release after merge (false = draft; tag is still created)
|
|
24
|
+
required: true
|
|
25
|
+
type: boolean
|
|
26
|
+
default: true
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
contents: write
|
|
30
|
+
pull-requests: write
|
|
31
|
+
issues: write
|
|
32
|
+
|
|
33
|
+
concurrency:
|
|
34
|
+
group: bump-version
|
|
35
|
+
cancel-in-progress: false
|
|
36
|
+
|
|
37
|
+
jobs:
|
|
38
|
+
bump:
|
|
39
|
+
name: Bump and open PR
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
env:
|
|
42
|
+
BUMP: ${{ inputs.bump }}
|
|
43
|
+
PUBLISH: ${{ inputs.publish }}
|
|
44
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
45
|
+
GH_REPO: ${{ github.repository }}
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
48
|
+
with:
|
|
49
|
+
ref: main
|
|
50
|
+
|
|
51
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
52
|
+
|
|
53
|
+
- id: versions
|
|
54
|
+
name: Compute next version
|
|
55
|
+
run: |
|
|
56
|
+
set -euo pipefail
|
|
57
|
+
|
|
58
|
+
OLD_VER="$(uv version --short --no-sync)"
|
|
59
|
+
OLD_VER="${OLD_VER%%$'\n'}"
|
|
60
|
+
NEW_VER="$(uv version --dry-run --bump "$BUMP" --short --no-sync)"
|
|
61
|
+
NEW_VER="${NEW_VER%%$'\n'}"
|
|
62
|
+
if [ -z "$NEW_VER" ]; then
|
|
63
|
+
echo "uv version --dry-run did not print a version" >&2
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
echo "Next version: ${OLD_VER} → ${NEW_VER} (bump=${BUMP})"
|
|
67
|
+
{
|
|
68
|
+
echo "old=${OLD_VER}"
|
|
69
|
+
echo "new=${NEW_VER}"
|
|
70
|
+
} >> "$GITHUB_OUTPUT"
|
|
71
|
+
|
|
72
|
+
- name: Bump ${{ steps.versions.outputs.old }} → ${{ steps.versions.outputs.new }}
|
|
73
|
+
env:
|
|
74
|
+
OLD_VER: ${{ steps.versions.outputs.old }}
|
|
75
|
+
NEW_VER: ${{ steps.versions.outputs.new }}
|
|
76
|
+
run: |
|
|
77
|
+
set -euo pipefail
|
|
78
|
+
|
|
79
|
+
if [ -n "$(git ls-remote --tags origin "refs/tags/v${NEW_VER}")" ]; then
|
|
80
|
+
echo "Tag v${NEW_VER} already exists; never retag." >&2
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
open_prs="$(gh pr list --head "bump/v${NEW_VER}" --state open --json number --jq 'length')"
|
|
85
|
+
if [ "$open_prs" != "0" ]; then
|
|
86
|
+
echo "An open PR already exists from bump/v${NEW_VER}." >&2
|
|
87
|
+
gh pr list --head "bump/v${NEW_VER}" --state open
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
uv version --bump "$BUMP" --no-sync
|
|
92
|
+
actual="$(uv version --short --no-sync)"
|
|
93
|
+
if [ "$actual" != "$NEW_VER" ]; then
|
|
94
|
+
echo "uv version wrote ${actual}, expected ${NEW_VER}" >&2
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
git checkout -b "bump/v${NEW_VER}"
|
|
99
|
+
|
|
100
|
+
git add pyproject.toml
|
|
101
|
+
if ! git diff --quiet -- uv.lock; then
|
|
102
|
+
git add uv.lock
|
|
103
|
+
fi
|
|
104
|
+
if git diff --cached --quiet; then
|
|
105
|
+
echo "uv version did not change pyproject.toml or uv.lock" >&2
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
git config user.name "github-actions[bot]"
|
|
110
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
111
|
+
git commit -m "chore: bump version to ${NEW_VER}." -m "Co-authored-by: Joseph Chiocchi <joe@yolk.cc>"
|
|
112
|
+
|
|
113
|
+
git push -u origin "HEAD:bump/v${NEW_VER}"
|
|
114
|
+
|
|
115
|
+
if [ -n "$(git ls-remote --tags origin "refs/tags/v${OLD_VER}")" ]; then
|
|
116
|
+
compare_base="v${OLD_VER}"
|
|
117
|
+
else
|
|
118
|
+
compare_base="main"
|
|
119
|
+
fi
|
|
120
|
+
OLD_TREE_URL="https://github.com/${GH_REPO}/tree/${compare_base}"
|
|
121
|
+
NEW_TREE_URL="https://github.com/${GH_REPO}/tree/bump/v${NEW_VER}"
|
|
122
|
+
COMPARE_URL="https://github.com/${GH_REPO}/compare/${compare_base}...bump/v${NEW_VER}"
|
|
123
|
+
echo "${OLD_TREE_URL}"
|
|
124
|
+
echo "${NEW_TREE_URL}"
|
|
125
|
+
echo "${COMPARE_URL}"
|
|
126
|
+
{
|
|
127
|
+
echo "Bump [${OLD_VER}](${OLD_TREE_URL}) → [${NEW_VER}](${NEW_TREE_URL})"
|
|
128
|
+
echo
|
|
129
|
+
echo "Compare: ${COMPARE_URL}"
|
|
130
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
131
|
+
|
|
132
|
+
if [ "$PUBLISH" = "true" ]; then
|
|
133
|
+
mode_comment="<!-- tomlrange-release: publish -->"
|
|
134
|
+
label="release:publish"
|
|
135
|
+
mode_word="published"
|
|
136
|
+
else
|
|
137
|
+
mode_comment="<!-- tomlrange-release: draft -->"
|
|
138
|
+
label="release:draft"
|
|
139
|
+
mode_word="draft"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
ensure_label() {
|
|
143
|
+
local name="$1" color="$2" desc="$3"
|
|
144
|
+
if gh label list --limit 100 --json name --jq '.[].name' | grep -Fxq "$name"; then
|
|
145
|
+
return 0
|
|
146
|
+
fi
|
|
147
|
+
gh label create "$name" --color "$color" --description "$desc"
|
|
148
|
+
}
|
|
149
|
+
ensure_label "release:publish" "0e8a16" "Cut a published GitHub Release when this bump PR merges"
|
|
150
|
+
ensure_label "release:draft" "fbca04" "Cut a draft GitHub Release when this bump PR merges"
|
|
151
|
+
|
|
152
|
+
{
|
|
153
|
+
echo "Bumps \`[project].version\` with \`uv version --bump ${BUMP}\` to **${NEW_VER}**."
|
|
154
|
+
echo
|
|
155
|
+
echo "${mode_comment}"
|
|
156
|
+
echo
|
|
157
|
+
echo "Merging this PR triggers **Cut release**, which creates GitHub Release \`v${NEW_VER}\` immediately (${mode_word})."
|
|
158
|
+
echo
|
|
159
|
+
echo "A published Release then runs **Publish** (TestPyPI; PyPI only when the version is not a pre-release). A draft does not, until someone publishes it in the UI."
|
|
160
|
+
echo
|
|
161
|
+
echo "Do not push tags by hand. Never retag."
|
|
162
|
+
} > "$RUNNER_TEMP/pr-body.md"
|
|
163
|
+
|
|
164
|
+
PR_URL="$(gh pr create \
|
|
165
|
+
--base main \
|
|
166
|
+
--head "bump/v${NEW_VER}" \
|
|
167
|
+
--title "chore: bump version to ${NEW_VER}" \
|
|
168
|
+
--label "$label" \
|
|
169
|
+
--body-file "$RUNNER_TEMP/pr-body.md")"
|
|
170
|
+
echo "${PR_URL}"
|
|
171
|
+
{
|
|
172
|
+
echo
|
|
173
|
+
echo "PR: [#${PR_URL##*/}](${PR_URL})"
|
|
174
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
20
|
+
with:
|
|
21
|
+
persist-credentials: false
|
|
22
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
23
|
+
- run: uv sync --group dev
|
|
24
|
+
- run: uv run ruff check
|
|
25
|
+
- run: uv run ruff format --check
|
|
26
|
+
- run: uv run pytest
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
name: Cut release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [closed]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
actions: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
cut:
|
|
13
|
+
name: Create GitHub Release
|
|
14
|
+
if: >
|
|
15
|
+
github.event.pull_request.merged == true &&
|
|
16
|
+
github.event.pull_request.base.ref == github.event.repository.default_branch &&
|
|
17
|
+
startsWith(github.event.pull_request.head.ref, 'bump/v')
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
env:
|
|
20
|
+
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
|
21
|
+
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
|
|
22
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
23
|
+
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
|
|
24
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
25
|
+
GH_REPO: ${{ github.repository }}
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
28
|
+
with:
|
|
29
|
+
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
|
30
|
+
|
|
31
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
32
|
+
|
|
33
|
+
- name: Create GitHub Release
|
|
34
|
+
run: |
|
|
35
|
+
set -euo pipefail
|
|
36
|
+
|
|
37
|
+
PR_BODY="$(gh pr view "$PR_NUMBER" --json body --jq .body)"
|
|
38
|
+
VER="$(python3 -c 'import tomllib, pathlib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
|
|
39
|
+
expected="${HEAD_REF#bump/v}"
|
|
40
|
+
if [ "$VER" != "$expected" ]; then
|
|
41
|
+
echo "pyproject version ${VER} does not match branch ${HEAD_REF} (expected ${expected})." >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
IS_PRERELEASE="$(VER="$VER" uv run --with packaging --no-project python -c 'import os; from packaging.version import Version; print(Version(os.environ["VER"]).is_prerelease)')"
|
|
46
|
+
pre_args=()
|
|
47
|
+
if [ "$IS_PRERELEASE" = "True" ]; then
|
|
48
|
+
pre_args+=(--prerelease)
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
has_pub_label=0
|
|
52
|
+
has_draft_label=0
|
|
53
|
+
case ",${PR_LABELS}," in
|
|
54
|
+
*",release:publish,"*) has_pub_label=1 ;;
|
|
55
|
+
esac
|
|
56
|
+
case ",${PR_LABELS}," in
|
|
57
|
+
*",release:draft,"*) has_draft_label=1 ;;
|
|
58
|
+
esac
|
|
59
|
+
|
|
60
|
+
mode=""
|
|
61
|
+
if [ "$has_pub_label" -eq 1 ] && [ "$has_draft_label" -eq 1 ]; then
|
|
62
|
+
echo "PR has both release:publish and release:draft; pick one." >&2
|
|
63
|
+
exit 1
|
|
64
|
+
elif [ "$has_pub_label" -eq 1 ]; then
|
|
65
|
+
mode=publish
|
|
66
|
+
elif [ "$has_draft_label" -eq 1 ]; then
|
|
67
|
+
mode=draft
|
|
68
|
+
elif printf '%s\n' "$PR_BODY" | grep -Fq '<!-- tomlrange-release: publish -->'; then
|
|
69
|
+
if printf '%s\n' "$PR_BODY" | grep -Fq '<!-- tomlrange-release: draft -->'; then
|
|
70
|
+
echo "PR body has both tomlrange-release comments; pick one." >&2
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
mode=publish
|
|
74
|
+
elif printf '%s\n' "$PR_BODY" | grep -Fq '<!-- tomlrange-release: draft -->'; then
|
|
75
|
+
mode=draft
|
|
76
|
+
else
|
|
77
|
+
echo "Cannot determine release mode: need label release:publish or release:draft, or HTML comment <!-- tomlrange-release: publish --> / <!-- tomlrange-release: draft -->." >&2
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
if [ -n "$(git ls-remote --tags origin "refs/tags/v${VER}")" ]; then
|
|
82
|
+
echo "Tag v${VER} already exists; never retag." >&2
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
echo "Cutting v${VER} as ${mode} at ${MERGE_SHA} (is_prerelease=${IS_PRERELEASE})"
|
|
87
|
+
if [ "$mode" = draft ]; then
|
|
88
|
+
gh release create "v${VER}" \
|
|
89
|
+
--draft \
|
|
90
|
+
"${pre_args[@]}" \
|
|
91
|
+
--generate-notes \
|
|
92
|
+
--title "v${VER}" \
|
|
93
|
+
--target "$MERGE_SHA"
|
|
94
|
+
echo "Draft: not dispatching Publish (UI publish still fires on: release)."
|
|
95
|
+
else
|
|
96
|
+
gh release create "v${VER}" \
|
|
97
|
+
"${pre_args[@]}" \
|
|
98
|
+
--generate-notes \
|
|
99
|
+
--title "v${VER}" \
|
|
100
|
+
--target "$MERGE_SHA"
|
|
101
|
+
|
|
102
|
+
# GITHUB_TOKEN creates do not fire on: release; workflow_dispatch does cascade.
|
|
103
|
+
if [ "$IS_PRERELEASE" = "True" ]; then
|
|
104
|
+
echo "Dispatching Publish (testpypi only) for pre-release v${VER}"
|
|
105
|
+
gh workflow run publish.yml --ref "v${VER}" -f target=testpypi -f release_tag="v${VER}"
|
|
106
|
+
echo "Dispatched workflow runs (listing may be racy):"
|
|
107
|
+
gh run list --workflow=publish.yml --event=workflow_dispatch --limit 2 || true
|
|
108
|
+
else
|
|
109
|
+
echo "Dispatching Publish (testpypi + pypi) for v${VER}"
|
|
110
|
+
gh workflow run publish.yml --ref "v${VER}" -f target=testpypi -f release_tag="v${VER}"
|
|
111
|
+
gh workflow run publish.yml --ref "v${VER}" -f target=pypi -f release_tag="v${VER}"
|
|
112
|
+
echo "Dispatched workflow runs (listing may be racy):"
|
|
113
|
+
gh run list --workflow=publish.yml --event=workflow_dispatch --limit 4 || true
|
|
114
|
+
fi
|
|
115
|
+
fi
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tomlrange
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Parse and validate { from, to } tables from decoded TOML.
|
|
5
5
|
Project-URL: Homepage, https://github.com/yyolk/tomlrange
|
|
6
6
|
Project-URL: Repository, https://github.com/yyolk/tomlrange
|
|
@@ -78,7 +78,7 @@ from tomlrange import Domain
|
|
|
78
78
|
Month = Domain(int, lo=1, hi=12, name="month")
|
|
79
79
|
|
|
80
80
|
data = tomllib.loads(config)
|
|
81
|
-
year = Month.bound(data["months"])
|
|
81
|
+
year = Month.bound(data["months"]) # 1..12
|
|
82
82
|
windows = Month.bounds(data["windows"]) # 1..4 and 6..10
|
|
83
83
|
|
|
84
84
|
assert list(year) == list(range(1, 13))
|
|
@@ -104,13 +104,15 @@ Or name the domain by subclassing `Spec` — the class body is the schema:
|
|
|
104
104
|
```python
|
|
105
105
|
from tomlrange import Spec
|
|
106
106
|
|
|
107
|
+
|
|
107
108
|
class Month(Spec):
|
|
108
109
|
typ = int
|
|
109
110
|
lo = 1
|
|
110
111
|
hi = 12
|
|
111
112
|
|
|
113
|
+
|
|
112
114
|
Month.parse({"from": 1, "to": 4})
|
|
113
|
-
Month.parse_many([{
|
|
115
|
+
Month.parse_many([{"from": 1, "to": 4}, {"from": 6, "to": 10}])
|
|
114
116
|
```
|
|
115
117
|
|
|
116
118
|
## Validation
|
|
@@ -127,6 +129,10 @@ On a list, `overlap=` is `"reject"` (default), `"allow"`, or `"merge"`.
|
|
|
127
129
|
Reject treats a shared endpoint as overlap (`1–4` and `4–6` fail).
|
|
128
130
|
Adjacent integers (`1–4` then `5–8`) are fine; `merge()` will coalesce them.
|
|
129
131
|
|
|
132
|
+
`list()`, `len()`, `width`, and `as_range()` are int-only — they raise
|
|
133
|
+
`TypeError` (`"{name} width is only defined for int"`). Membership,
|
|
134
|
+
`as_tuple()`, and `as_table()` work for any ordered `typ`.
|
|
135
|
+
|
|
130
136
|
Errors carry a path:
|
|
131
137
|
|
|
132
138
|
```
|
|
@@ -140,6 +146,7 @@ months_ranges[1].to: 13 is above month 12
|
|
|
140
146
|
- Not a two-element array (`[1, 12]`). After TOML decode that is a list, not a table.
|
|
141
147
|
- Not wrap-around (`from = 11, to = 2`). Write two spans.
|
|
142
148
|
- Not `step`. A range table is a closed interval.
|
|
149
|
+
- Not iteration over a non-int domain. Walk dates or strings yourself.
|
|
143
150
|
|
|
144
151
|
## Install
|
|
145
152
|
|
|
@@ -38,7 +38,7 @@ from tomlrange import Domain
|
|
|
38
38
|
Month = Domain(int, lo=1, hi=12, name="month")
|
|
39
39
|
|
|
40
40
|
data = tomllib.loads(config)
|
|
41
|
-
year = Month.bound(data["months"])
|
|
41
|
+
year = Month.bound(data["months"]) # 1..12
|
|
42
42
|
windows = Month.bounds(data["windows"]) # 1..4 and 6..10
|
|
43
43
|
|
|
44
44
|
assert list(year) == list(range(1, 13))
|
|
@@ -64,13 +64,15 @@ Or name the domain by subclassing `Spec` — the class body is the schema:
|
|
|
64
64
|
```python
|
|
65
65
|
from tomlrange import Spec
|
|
66
66
|
|
|
67
|
+
|
|
67
68
|
class Month(Spec):
|
|
68
69
|
typ = int
|
|
69
70
|
lo = 1
|
|
70
71
|
hi = 12
|
|
71
72
|
|
|
73
|
+
|
|
72
74
|
Month.parse({"from": 1, "to": 4})
|
|
73
|
-
Month.parse_many([{
|
|
75
|
+
Month.parse_many([{"from": 1, "to": 4}, {"from": 6, "to": 10}])
|
|
74
76
|
```
|
|
75
77
|
|
|
76
78
|
## Validation
|
|
@@ -87,6 +89,10 @@ On a list, `overlap=` is `"reject"` (default), `"allow"`, or `"merge"`.
|
|
|
87
89
|
Reject treats a shared endpoint as overlap (`1–4` and `4–6` fail).
|
|
88
90
|
Adjacent integers (`1–4` then `5–8`) are fine; `merge()` will coalesce them.
|
|
89
91
|
|
|
92
|
+
`list()`, `len()`, `width`, and `as_range()` are int-only — they raise
|
|
93
|
+
`TypeError` (`"{name} width is only defined for int"`). Membership,
|
|
94
|
+
`as_tuple()`, and `as_table()` work for any ordered `typ`.
|
|
95
|
+
|
|
90
96
|
Errors carry a path:
|
|
91
97
|
|
|
92
98
|
```
|
|
@@ -100,6 +106,7 @@ months_ranges[1].to: 13 is above month 12
|
|
|
100
106
|
- Not a two-element array (`[1, 12]`). After TOML decode that is a list, not a table.
|
|
101
107
|
- Not wrap-around (`from = 11, to = 2`). Write two spans.
|
|
102
108
|
- Not `step`. A range table is a closed interval.
|
|
109
|
+
- Not iteration over a non-int domain. Walk dates or strings yourself.
|
|
103
110
|
|
|
104
111
|
## Install
|
|
105
112
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tomlrange"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "Parse and validate { from, to } tables from decoded TOML."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -49,6 +49,7 @@ explicit = true
|
|
|
49
49
|
dev = [
|
|
50
50
|
"pytest>=8.0",
|
|
51
51
|
"ruff>=0.16.8",
|
|
52
|
+
"pre-commit>=4.0",
|
|
52
53
|
]
|
|
53
54
|
|
|
54
55
|
[tool.pytest.ini_options]
|
|
@@ -74,3 +75,6 @@ select = [
|
|
|
74
75
|
"TC",
|
|
75
76
|
]
|
|
76
77
|
ignore = ["E501"]
|
|
78
|
+
|
|
79
|
+
[tool.ruff.lint.flake8-type-checking]
|
|
80
|
+
runtime-evaluated-decorators = ["dataclasses.dataclass"]
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
"""Parse and validate `{ from = …, to = … }` tables from decoded TOML."""
|
|
2
2
|
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
3
5
|
from tomlrange.bound import Bound, Bounds
|
|
4
6
|
from tomlrange.domain import Domain, Spec
|
|
5
7
|
from tomlrange.error import TomlRangeError
|
|
6
8
|
|
|
9
|
+
try:
|
|
10
|
+
__version__ = version("tomlrange")
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
__version__ = "unknown"
|
|
13
|
+
|
|
7
14
|
__all__ = [
|
|
8
15
|
"Bound",
|
|
9
16
|
"Bounds",
|
|
@@ -11,4 +18,3 @@ __all__ = [
|
|
|
11
18
|
"Spec",
|
|
12
19
|
"TomlRangeError",
|
|
13
20
|
]
|
|
14
|
-
__version__ = "0.2.0"
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
from collections.abc import Iterator, Mapping
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
5
|
from tomlrange.domain import Domain
|
|
6
6
|
from tomlrange.error import TomlRangeError
|
|
7
|
-
from tomlrange.paths import
|
|
7
|
+
from tomlrange.paths import index_path, join_path
|
|
8
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from tomlrange.paths import Overlap
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
|
|
13
|
+
def _as_table(raw: Any, *, path: str, keys: tuple[str, str]) -> Mapping[str, Any]:
|
|
11
14
|
if isinstance(raw, Mapping):
|
|
12
15
|
return raw
|
|
13
|
-
|
|
16
|
+
start_key, stop_key = keys
|
|
17
|
+
raise TomlRangeError(
|
|
18
|
+
path,
|
|
19
|
+
f"expected a table {{ {start_key} = …, {stop_key} = … }}",
|
|
20
|
+
raw,
|
|
21
|
+
)
|
|
14
22
|
|
|
15
23
|
|
|
16
24
|
@dataclass(frozen=True, slots=True)
|
|
@@ -23,8 +31,8 @@ class Bound[T]:
|
|
|
23
31
|
|
|
24
32
|
@classmethod
|
|
25
33
|
def parse(cls, raw: Any, domain: Domain[T], *, path: str = ".") -> Bound[T]:
|
|
26
|
-
table = _as_table(raw, path=path)
|
|
27
34
|
start_key, stop_key = domain.keys
|
|
35
|
+
table = _as_table(raw, path=path, keys=domain.keys)
|
|
28
36
|
extra = set(table) - {start_key, stop_key}
|
|
29
37
|
if extra:
|
|
30
38
|
raise TomlRangeError(
|
|
@@ -48,16 +56,18 @@ class Bound[T]:
|
|
|
48
56
|
)
|
|
49
57
|
return cls(start, stop, domain)
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
def width(self) -> int:
|
|
59
|
+
def _require_int(self) -> None:
|
|
53
60
|
if type(self.start) is not int or type(self.stop) is not int:
|
|
54
61
|
raise TypeError(f"{self.domain.name} width is only defined for int")
|
|
55
|
-
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def width(self) -> int:
|
|
65
|
+
self._require_int()
|
|
66
|
+
return self.stop - self.start + 1 # type: ignore[operator]
|
|
56
67
|
|
|
57
68
|
def as_range(self) -> range:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return range(self.start, self.stop + 1)
|
|
69
|
+
self._require_int()
|
|
70
|
+
return range(self.start, self.stop + 1) # type: ignore[arg-type]
|
|
61
71
|
|
|
62
72
|
def as_tuple(self) -> tuple[T, T]:
|
|
63
73
|
return (self.start, self.stop)
|
|
@@ -72,9 +82,11 @@ class Bound[T]:
|
|
|
72
82
|
return self.start <= item <= self.stop # type: ignore[operator]
|
|
73
83
|
|
|
74
84
|
def __iter__(self) -> Iterator[T]:
|
|
85
|
+
self._require_int()
|
|
75
86
|
yield from self.as_range() # type: ignore[misc]
|
|
76
87
|
|
|
77
88
|
def __len__(self) -> int:
|
|
89
|
+
self._require_int()
|
|
78
90
|
return self.width
|
|
79
91
|
|
|
80
92
|
def overlaps(self, other: Bound[T]) -> bool:
|
|
@@ -145,6 +157,10 @@ class Bounds[T]:
|
|
|
145
157
|
def merge(self) -> Bounds[T]:
|
|
146
158
|
return Bounds(_coalesce(self.spans, self.domain), self.domain)
|
|
147
159
|
|
|
160
|
+
def _require_int(self) -> None:
|
|
161
|
+
if self.domain.typ is not int:
|
|
162
|
+
raise TypeError(f"{self.domain.name} width is only defined for int")
|
|
163
|
+
|
|
148
164
|
def covers(self, item: object) -> bool:
|
|
149
165
|
return any(item in span for span in self.spans)
|
|
150
166
|
|
|
@@ -152,6 +168,7 @@ class Bounds[T]:
|
|
|
152
168
|
return self.covers(item)
|
|
153
169
|
|
|
154
170
|
def __iter__(self) -> Iterator[T]:
|
|
171
|
+
self._require_int()
|
|
155
172
|
seen: set[T] = set()
|
|
156
173
|
for span in self.spans:
|
|
157
174
|
for item in span:
|
|
@@ -161,6 +178,7 @@ class Bounds[T]:
|
|
|
161
178
|
yield item
|
|
162
179
|
|
|
163
180
|
def __len__(self) -> int:
|
|
181
|
+
self._require_int()
|
|
164
182
|
return sum(1 for _ in self)
|
|
165
183
|
|
|
166
184
|
def __repr__(self) -> str:
|
|
@@ -168,7 +186,9 @@ class Bounds[T]:
|
|
|
168
186
|
return f"Bounds({inner}, {self.domain.name})"
|
|
169
187
|
|
|
170
188
|
|
|
171
|
-
def _coalesce[T](
|
|
189
|
+
def _coalesce[T](
|
|
190
|
+
spans: tuple[Bound[T], ...], domain: Domain[T]
|
|
191
|
+
) -> tuple[Bound[T], ...]:
|
|
172
192
|
if not spans:
|
|
173
193
|
return ()
|
|
174
194
|
ordered = sorted(spans, key=lambda s: (s.start, s.stop))
|
|
@@ -2,10 +2,10 @@ from dataclasses import dataclass
|
|
|
2
2
|
from typing import TYPE_CHECKING, Any
|
|
3
3
|
|
|
4
4
|
from tomlrange.error import TomlRangeError
|
|
5
|
-
from tomlrange.paths import Overlap
|
|
6
5
|
|
|
7
6
|
if TYPE_CHECKING:
|
|
8
7
|
from tomlrange.bound import Bound, Bounds
|
|
8
|
+
from tomlrange.paths import Overlap
|
|
9
9
|
|
|
10
10
|
_FROM = "from"
|
|
11
11
|
_TO = "to"
|
|
@@ -64,8 +64,8 @@ class Domain[T]:
|
|
|
64
64
|
)
|
|
65
65
|
return raw
|
|
66
66
|
|
|
67
|
-
def bound(self, raw: Any, *, path: str = ".") ->
|
|
68
|
-
from tomlrange.bound import Bound
|
|
67
|
+
def bound(self, raw: Any, *, path: str = ".") -> Bound[T]:
|
|
68
|
+
from tomlrange.bound import Bound # cycle: Domain ↔ Bound
|
|
69
69
|
|
|
70
70
|
return Bound.parse(raw, self, path=path)
|
|
71
71
|
|
|
@@ -75,13 +75,13 @@ class Domain[T]:
|
|
|
75
75
|
*,
|
|
76
76
|
path: str = ".",
|
|
77
77
|
overlap: Overlap = "reject",
|
|
78
|
-
) ->
|
|
79
|
-
from tomlrange.bound import Bounds
|
|
78
|
+
) -> Bounds[T]:
|
|
79
|
+
from tomlrange.bound import Bounds # cycle: Domain ↔ Bound
|
|
80
80
|
|
|
81
81
|
return Bounds.parse(raw, self, path=path, overlap=overlap)
|
|
82
82
|
|
|
83
|
-
def full(self) ->
|
|
84
|
-
from tomlrange.bound import Bound
|
|
83
|
+
def full(self) -> Bound[T]:
|
|
84
|
+
from tomlrange.bound import Bound # cycle: Domain ↔ Bound
|
|
85
85
|
|
|
86
86
|
if self.lo is None or self.hi is None:
|
|
87
87
|
raise TomlRangeError(".", f"{self.name} domain has no closed lo/hi")
|
|
@@ -129,8 +129,8 @@ class Spec:
|
|
|
129
129
|
)
|
|
130
130
|
|
|
131
131
|
@classmethod
|
|
132
|
-
def parse(cls, raw: Any, *, path: str = ".") ->
|
|
133
|
-
from tomlrange.bound import Bound
|
|
132
|
+
def parse(cls, raw: Any, *, path: str = ".") -> Bound[Any]:
|
|
133
|
+
from tomlrange.bound import Bound # cycle: Domain ↔ Bound
|
|
134
134
|
|
|
135
135
|
return Bound.parse(raw, cls.domain, path=path)
|
|
136
136
|
|
|
@@ -141,8 +141,8 @@ class Spec:
|
|
|
141
141
|
*,
|
|
142
142
|
path: str = ".",
|
|
143
143
|
overlap: Overlap | None = None,
|
|
144
|
-
) ->
|
|
145
|
-
from tomlrange.bound import Bounds
|
|
144
|
+
) -> Bounds[Any]:
|
|
145
|
+
from tomlrange.bound import Bounds # cycle: Domain ↔ Bound
|
|
146
146
|
|
|
147
147
|
return Bounds.parse(
|
|
148
148
|
raw,
|
|
@@ -152,5 +152,5 @@ class Spec:
|
|
|
152
152
|
)
|
|
153
153
|
|
|
154
154
|
@classmethod
|
|
155
|
-
def full(cls) ->
|
|
155
|
+
def full(cls) -> Bound[Any]:
|
|
156
156
|
return cls.domain.full()
|
|
File without changes
|
|
@@ -122,6 +122,31 @@ def test_custom_keys() -> None:
|
|
|
122
122
|
hours.bound({"from": 7, "to": 16})
|
|
123
123
|
with pytest.raises(TomlRangeError, match="must have keys"):
|
|
124
124
|
hours.bound({"start": 7})
|
|
125
|
+
with pytest.raises(
|
|
126
|
+
TomlRangeError, match=r"expected a table \{ start = …, end = … \}"
|
|
127
|
+
):
|
|
128
|
+
hours.bound([7, 16])
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_int_only_iteration() -> None:
|
|
132
|
+
labels = Domain(str, name="label")
|
|
133
|
+
bound = labels.bound({"from": "a", "to": "c"})
|
|
134
|
+
assert bound.as_tuple() == ("a", "c")
|
|
135
|
+
assert "b" in bound
|
|
136
|
+
with pytest.raises(TypeError, match="label width is only defined for int"):
|
|
137
|
+
list(bound)
|
|
138
|
+
with pytest.raises(TypeError, match="label width is only defined for int"):
|
|
139
|
+
len(bound)
|
|
140
|
+
with pytest.raises(TypeError, match="label width is only defined for int"):
|
|
141
|
+
_ = bound.width
|
|
142
|
+
with pytest.raises(TypeError, match="label width is only defined for int"):
|
|
143
|
+
bound.as_range()
|
|
144
|
+
spans = labels.bounds([{"from": "a", "to": "b"}, {"from": "d", "to": "e"}])
|
|
145
|
+
assert "a" in spans
|
|
146
|
+
with pytest.raises(TypeError, match="label width is only defined for int"):
|
|
147
|
+
list(spans)
|
|
148
|
+
with pytest.raises(TypeError, match="label width is only defined for int"):
|
|
149
|
+
len(spans)
|
|
125
150
|
|
|
126
151
|
|
|
127
152
|
def test_error_paths() -> None:
|
|
@@ -131,3 +156,80 @@ def test_error_paths() -> None:
|
|
|
131
156
|
path="months_ranges",
|
|
132
157
|
)
|
|
133
158
|
assert exc.value.path == "months_ranges[1].to"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_open_int_domain_parse() -> None:
|
|
162
|
+
open_int = Domain(int, name="n")
|
|
163
|
+
bound = open_int.bound({"from": -3, "to": 99})
|
|
164
|
+
assert bound.as_tuple() == (-3, 99)
|
|
165
|
+
assert -3 in bound
|
|
166
|
+
assert 99 in bound
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_full_requires_closed_lo_hi() -> None:
|
|
170
|
+
open_int = Domain(int, name="n")
|
|
171
|
+
with pytest.raises(TomlRangeError, match="n domain has no closed lo/hi"):
|
|
172
|
+
open_int.full()
|
|
173
|
+
half = Domain(int, lo=1, name="n")
|
|
174
|
+
with pytest.raises(TomlRangeError, match="n domain has no closed lo/hi"):
|
|
175
|
+
half.full()
|
|
176
|
+
|
|
177
|
+
class Open(Spec):
|
|
178
|
+
typ = int
|
|
179
|
+
name = "open"
|
|
180
|
+
|
|
181
|
+
with pytest.raises(TomlRangeError, match="open domain has no closed lo/hi"):
|
|
182
|
+
Open.full()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_bounds_empty_list() -> None:
|
|
186
|
+
empty = MONTH.bounds([])
|
|
187
|
+
assert empty.spans == ()
|
|
188
|
+
assert list(empty) == []
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_unknown_overlap_policy() -> None:
|
|
192
|
+
with pytest.raises(ValueError, match="unknown overlap policy 'bogus'"):
|
|
193
|
+
MONTH.bounds([{"from": 1, "to": 2}], overlap="bogus")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_float_domain_parse() -> None:
|
|
197
|
+
ratio = Domain(float, lo=0.0, hi=1.0, name="ratio")
|
|
198
|
+
bound = ratio.bound({"from": 0.25, "to": 0.75})
|
|
199
|
+
assert bound.as_tuple() == (0.25, 0.75)
|
|
200
|
+
assert 0.5 in bound
|
|
201
|
+
assert bound.as_table() == {"from": 0.25, "to": 0.75}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_spec_name_suffix_stripping() -> None:
|
|
205
|
+
class MonthBounds(Spec):
|
|
206
|
+
typ = int
|
|
207
|
+
lo = 1
|
|
208
|
+
hi = 12
|
|
209
|
+
|
|
210
|
+
class MonthRange(Spec):
|
|
211
|
+
typ = int
|
|
212
|
+
lo = 1
|
|
213
|
+
hi = 12
|
|
214
|
+
|
|
215
|
+
class MonthSpec(Spec):
|
|
216
|
+
typ = int
|
|
217
|
+
lo = 1
|
|
218
|
+
hi = 12
|
|
219
|
+
|
|
220
|
+
assert MonthBounds.domain.name == "month"
|
|
221
|
+
assert MonthRange.domain.name == "month"
|
|
222
|
+
assert MonthSpec.domain.name == "month"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_spec_overlap_class_default() -> None:
|
|
226
|
+
class AllowingMonth(Spec):
|
|
227
|
+
typ = int
|
|
228
|
+
lo = 1
|
|
229
|
+
hi = 12
|
|
230
|
+
overlap = "allow"
|
|
231
|
+
|
|
232
|
+
raw = [{"from": 1, "to": 4}, {"from": 4, "to": 6}]
|
|
233
|
+
allowed = AllowingMonth.parse_many(raw)
|
|
234
|
+
assert list(allowed) == [1, 2, 3, 4, 5, 6]
|
|
235
|
+
assert len(allowed.spans) == 2
|
tomlrange-0.2.2/uv.lock
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.14"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "cfgv"
|
|
7
|
+
version = "3.5.0"
|
|
8
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
9
|
+
sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" }
|
|
10
|
+
wheels = [
|
|
11
|
+
{ url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "colorama"
|
|
16
|
+
version = "0.4.6"
|
|
17
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
18
|
+
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" }
|
|
19
|
+
wheels = [
|
|
20
|
+
{ 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" },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[[package]]
|
|
24
|
+
name = "distlib"
|
|
25
|
+
version = "0.4.3"
|
|
26
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
27
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" }
|
|
28
|
+
wheels = [
|
|
29
|
+
{ url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[[package]]
|
|
33
|
+
name = "filelock"
|
|
34
|
+
version = "3.32.7"
|
|
35
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
36
|
+
sdist = { url = "https://files.pythonhosted.org/packages/0f/59/e19834834cb01a32febfbb0f8a23a9088088f5d45991824ff2bc3b5e8acb/filelock-3.32.7.tar.gz", hash = "sha256:37b8a3d9811b0f9aef7e5ec5c71bb320de52df51e6ca9bcd6f5ad81187660da7", size = 225154, upload-time = "2026-09-16T00:24:20.907Z" }
|
|
37
|
+
wheels = [
|
|
38
|
+
{ url = "https://files.pythonhosted.org/packages/15/df/31098c5aeb4d966b553641472bd55fcf5fdfac953549894b8a765ba44e91/filelock-3.32.7-py3-none-any.whl", hash = "sha256:65ff0d0190ea42038b32bda4b77834fb05be2cad4c5b9b01aa4dfb3614536e52", size = 100157, upload-time = "2026-09-16T00:24:19.543Z" },
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[[package]]
|
|
42
|
+
name = "identify"
|
|
43
|
+
version = "2.6.19"
|
|
44
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
45
|
+
sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" }
|
|
46
|
+
wheels = [
|
|
47
|
+
{ url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" },
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[[package]]
|
|
51
|
+
name = "iniconfig"
|
|
52
|
+
version = "2.3.0"
|
|
53
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
54
|
+
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" }
|
|
55
|
+
wheels = [
|
|
56
|
+
{ 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" },
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[[package]]
|
|
60
|
+
name = "nodeenv"
|
|
61
|
+
version = "1.10.0"
|
|
62
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
63
|
+
sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
|
|
64
|
+
wheels = [
|
|
65
|
+
{ url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[[package]]
|
|
69
|
+
name = "packaging"
|
|
70
|
+
version = "26.3"
|
|
71
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
72
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
|
|
73
|
+
wheels = [
|
|
74
|
+
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[[package]]
|
|
78
|
+
name = "platformdirs"
|
|
79
|
+
version = "4.11.9"
|
|
80
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
81
|
+
sdist = { url = "https://files.pythonhosted.org/packages/58/b9/8adc4e1b422b27fd88540ec7bf1f406f77ef393ec070e26fc430e914cde8/platformdirs-4.11.9.tar.gz", hash = "sha256:e2c66a8d384596cd98e3c4aea2d761df7bac95d9d8a2cc3946daa8cdafdaebc1", size = 38345, upload-time = "2026-09-16T13:31:45.259Z" }
|
|
82
|
+
wheels = [
|
|
83
|
+
{ url = "https://files.pythonhosted.org/packages/f3/94/803ba86705257d7eedddac4b02eb88a7483b1600e9200c1fefc6f1a9a3ff/platformdirs-4.11.9-py3-none-any.whl", hash = "sha256:0a3958f58a9e30321eaef0a424dd0b77cce242886b36b8aa992f9731ef2d59c1", size = 24472, upload-time = "2026-09-16T13:31:44.049Z" },
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
[[package]]
|
|
87
|
+
name = "pluggy"
|
|
88
|
+
version = "1.6.0"
|
|
89
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
90
|
+
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" }
|
|
91
|
+
wheels = [
|
|
92
|
+
{ 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" },
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[[package]]
|
|
96
|
+
name = "pre-commit"
|
|
97
|
+
version = "4.6.2"
|
|
98
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
99
|
+
dependencies = [
|
|
100
|
+
{ name = "cfgv" },
|
|
101
|
+
{ name = "identify" },
|
|
102
|
+
{ name = "nodeenv" },
|
|
103
|
+
{ name = "pyyaml" },
|
|
104
|
+
{ name = "virtualenv" },
|
|
105
|
+
]
|
|
106
|
+
sdist = { url = "https://files.pythonhosted.org/packages/74/89/1f3e8e1fc3e97de0fa963495832f581f025f29471602a309e48808244292/pre_commit-4.6.2.tar.gz", hash = "sha256:8f5d7bfb021ecdbcd9d49d89847082dd24172ccde534390081a679ad046e2441", size = 198670, upload-time = "2026-08-10T22:07:18.421Z" }
|
|
107
|
+
wheels = [
|
|
108
|
+
{ url = "https://files.pythonhosted.org/packages/45/e2/bbb7129c9e7999a6b8ee9cca3b66486c25c423ab5a75f34071798b74ce94/pre_commit-4.6.2-py2.py3-none-any.whl", hash = "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e", size = 226202, upload-time = "2026-08-10T22:07:16.942Z" },
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
[[package]]
|
|
112
|
+
name = "pygments"
|
|
113
|
+
version = "2.21.0"
|
|
114
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
115
|
+
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
|
|
116
|
+
wheels = [
|
|
117
|
+
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
[[package]]
|
|
121
|
+
name = "pytest"
|
|
122
|
+
version = "9.1.1"
|
|
123
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
124
|
+
dependencies = [
|
|
125
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
126
|
+
{ name = "iniconfig" },
|
|
127
|
+
{ name = "packaging" },
|
|
128
|
+
{ name = "pluggy" },
|
|
129
|
+
{ name = "pygments" },
|
|
130
|
+
]
|
|
131
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
|
|
132
|
+
wheels = [
|
|
133
|
+
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
[[package]]
|
|
137
|
+
name = "python-discovery"
|
|
138
|
+
version = "1.6.0"
|
|
139
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
140
|
+
dependencies = [
|
|
141
|
+
{ name = "filelock" },
|
|
142
|
+
]
|
|
143
|
+
sdist = { url = "https://files.pythonhosted.org/packages/91/96/0f93e27c9f60a650838f2118159aa115fd5732c0716247917b7ba7ede665/python_discovery-1.6.0.tar.gz", hash = "sha256:6393b4eae1be8b2182670635e7baff89ac21cb9f8e86fd1ff40c7b1144febb4c", size = 82849, upload-time = "2026-08-28T17:30:02.366Z" }
|
|
144
|
+
wheels = [
|
|
145
|
+
{ url = "https://files.pythonhosted.org/packages/43/5e/21abf578182fb15006a57faf3711a1e659e29d600d19b6e557eae908c81d/python_discovery-1.6.0-py3-none-any.whl", hash = "sha256:d4e244cf17b8b29819ed78003d55fbacf86eda23425b075454fff9271b79377a", size = 38451, upload-time = "2026-08-28T17:30:01.236Z" },
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
[[package]]
|
|
149
|
+
name = "pyyaml"
|
|
150
|
+
version = "6.0.3"
|
|
151
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
152
|
+
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
|
153
|
+
wheels = [
|
|
154
|
+
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
|
|
155
|
+
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
|
|
156
|
+
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
|
|
157
|
+
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
|
|
158
|
+
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
|
|
159
|
+
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
|
|
160
|
+
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
|
|
161
|
+
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
|
|
162
|
+
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
|
|
163
|
+
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
|
|
164
|
+
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
|
|
165
|
+
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
|
|
166
|
+
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
|
|
167
|
+
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
|
|
168
|
+
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
|
|
169
|
+
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
|
|
170
|
+
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
|
|
171
|
+
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
[[package]]
|
|
175
|
+
name = "ruff"
|
|
176
|
+
version = "0.16.8"
|
|
177
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
178
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ba/78/449cb84790bd5cc3823b2652ee405a4558856e5c4195aee3a16bf7b3eb5d/ruff-0.16.8.tar.gz", hash = "sha256:9247bf92b5f04d825c8639a4fe423ec2e4222acd9222e58412b0dab7e442798b", size = 4938814, upload-time = "2026-09-16T15:54:46.688Z" }
|
|
179
|
+
wheels = [
|
|
180
|
+
{ url = "https://files.pythonhosted.org/packages/ac/25/6071aabc530e9be7e2c195e8fe3f7aea2735405b6cf447212832d7811831/ruff-0.16.8-py3-none-linux_armv6l.whl", hash = "sha256:6ffbd6d87383c1edf5f6fa890f10200950240d7c1a16052a19a09d3a2307dd38", size = 10048966, upload-time = "2026-09-16T15:53:57.605Z" },
|
|
181
|
+
{ url = "https://files.pythonhosted.org/packages/54/98/07f90ecbc74dd5fb5764f11f2bc774d6a7cffef92d2ff5f5b4e9e23c754e/ruff-0.16.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:42ed6b878ed61e3acca92f2730a17acff39286944ea82398544696366a6f925e", size = 10165498, upload-time = "2026-09-16T15:54:01.14Z" },
|
|
182
|
+
{ url = "https://files.pythonhosted.org/packages/fe/1f/e6a712e3b47cad4a40600134105ed193cb773f618a42eb7ba323cb812cc0/ruff-0.16.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ea781c7f2afba8c6a505ea0fb3f994020249e0c450635f5381286fea6b46170", size = 9830004, upload-time = "2026-09-16T15:54:03.998Z" },
|
|
183
|
+
{ url = "https://files.pythonhosted.org/packages/23/f2/311a08776d75d81c7676e20b6b020ae63cbe881fcdc7a8dd64e6e18bdd93/ruff-0.16.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8efeae3bbe414a5efefda11a792dfb51ef90ac48d50c4830de2f644caf3e8659", size = 9986558, upload-time = "2026-09-16T15:54:06.804Z" },
|
|
184
|
+
{ url = "https://files.pythonhosted.org/packages/f3/ed/37b6cb3d3ba8c73e68ae3eb1d502383beb5aa05a582bb7bb3a922f929f54/ruff-0.16.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a79b795469fef7fc6e908b218eed2eb17332afd85031db6480dc864560e69b2", size = 9877332, upload-time = "2026-09-16T15:54:09.552Z" },
|
|
185
|
+
{ url = "https://files.pythonhosted.org/packages/22/cc/40873a8f36ad084cc540d55fcca7077264d5b13b24659e9180c176fb2b08/ruff-0.16.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fdc5563cdc50555e6fba39322850860e9267c1b3d12c26a74729d8604c3c812", size = 10507125, upload-time = "2026-09-16T15:54:12.152Z" },
|
|
186
|
+
{ url = "https://files.pythonhosted.org/packages/c3/e4/fc91a642b78ccbab6b9477720f3644ae7a10a9bcce69a934679cd64f62bc/ruff-0.16.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34508983c70665578dab88f5223d8e6228307e1135398ca8bfc8b7e9501e282b", size = 11336694, upload-time = "2026-09-16T15:54:15.489Z" },
|
|
187
|
+
{ url = "https://files.pythonhosted.org/packages/c2/3d/bbd2a9a600a4e73dc3e7548a249c8d1671273464b55822c6fae50f602dff/ruff-0.16.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644bb578569e0ffc575741232bd385dacdd6fbe123f1a729e7a225f54aa3957f", size = 10774448, upload-time = "2026-09-16T15:54:18.16Z" },
|
|
188
|
+
{ url = "https://files.pythonhosted.org/packages/1a/41/d83af9879a7b6e8bf5fe16b1da0b134049d2f5d3afac12defb0897cb84bd/ruff-0.16.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15e7d226246961db9235098333caa13063906d3851136b84c2900b82f5daa1df", size = 10323796, upload-time = "2026-09-16T15:54:20.743Z" },
|
|
189
|
+
{ url = "https://files.pythonhosted.org/packages/f5/2c/cefd07bfe914b84943ea769ade8d607bd22750b965d3228eefd7cebd15d0/ruff-0.16.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a2bf6bc3e9ebdd4449abc6f06cf64b98051a2c61cf94d2fe9596518c881f1a1e", size = 10514115, upload-time = "2026-09-16T15:54:23.497Z" },
|
|
190
|
+
{ url = "https://files.pythonhosted.org/packages/f3/9d/76a2e26c79a23be6e6e3664c57bec9e9fc8de155cfb9e4b67ea91b64f9d7/ruff-0.16.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ca111ba0849539165e9e59d2b442542f3c1e8060ebbdea82494f1ffbccb1e1f", size = 10072582, upload-time = "2026-09-16T15:54:26.185Z" },
|
|
191
|
+
{ url = "https://files.pythonhosted.org/packages/2e/d4/f42edddb39668af1a559ceafa3823aedd65633a48dc9768e775485faa2c1/ruff-0.16.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:359a1e5b495448ee1e91018064382ebc86f90e8aac2fed222c7d0e4e8df85fd2", size = 9879644, upload-time = "2026-09-16T15:54:29.278Z" },
|
|
192
|
+
{ url = "https://files.pythonhosted.org/packages/f8/d4/913e3195d95e0378786c6656945c865f534a3560e29139da4882aff630d1/ruff-0.16.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:59e8f5681349474110b24d62e93cfda6593f5fa3473446ca3705200cac1a08b9", size = 10231569, upload-time = "2026-09-16T15:54:32.036Z" },
|
|
193
|
+
{ url = "https://files.pythonhosted.org/packages/2b/c4/8aa6ea0bdcedbd1bf87397e2fc4ed8406448ea5842f8660bc6e5f163039d/ruff-0.16.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:efa3e7a16d1baaa79957888dfdf8be9ef2e44db81cb032af06d76632ab59e773", size = 10663666, upload-time = "2026-09-16T15:54:34.838Z" },
|
|
194
|
+
{ url = "https://files.pythonhosted.org/packages/3d/02/7f10ef4700bc223c30a3fdd10631a29830c45524b810a3c7ed947af64591/ruff-0.16.8-py3-none-win32.whl", hash = "sha256:55793ba85c69921e89be061426d91a78652d6e50317c962240922747a4eb713f", size = 10093472, upload-time = "2026-09-16T15:54:37.47Z" },
|
|
195
|
+
{ url = "https://files.pythonhosted.org/packages/1e/5d/a509c07d714b6da88f2c518b4637cf6f1d46b074be8f0f1e5fb9ff5126fe/ruff-0.16.8-py3-none-win_amd64.whl", hash = "sha256:a6b85621fd3c81e31fc5f5add09c9c078b430db3595ca632efafdec9e64ebfaa", size = 10586899, upload-time = "2026-09-16T15:54:40.488Z" },
|
|
196
|
+
{ url = "https://files.pythonhosted.org/packages/fe/a0/50787329e4f20bf9dc9f6230015d46ec69c51a97ace5bc202dae4755365d/ruff-0.16.8-py3-none-win_arm64.whl", hash = "sha256:d075e820af612102ce217f07cc93e69f9490b10ec13ea85fa87bd03d996cef8a", size = 10386316, upload-time = "2026-09-16T15:54:43.332Z" },
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
[[package]]
|
|
200
|
+
name = "tomlrange"
|
|
201
|
+
version = "0.2.2"
|
|
202
|
+
source = { editable = "." }
|
|
203
|
+
|
|
204
|
+
[package.dev-dependencies]
|
|
205
|
+
dev = [
|
|
206
|
+
{ name = "pre-commit" },
|
|
207
|
+
{ name = "pytest" },
|
|
208
|
+
{ name = "ruff" },
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
[package.metadata]
|
|
212
|
+
|
|
213
|
+
[package.metadata.requires-dev]
|
|
214
|
+
dev = [
|
|
215
|
+
{ name = "pre-commit", specifier = ">=4.0" },
|
|
216
|
+
{ name = "pytest", specifier = ">=8.0" },
|
|
217
|
+
{ name = "ruff", specifier = ">=0.16.8" },
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
[[package]]
|
|
221
|
+
name = "virtualenv"
|
|
222
|
+
version = "21.7.10"
|
|
223
|
+
source = { registry = "https://pypi.org/simple/" }
|
|
224
|
+
dependencies = [
|
|
225
|
+
{ name = "distlib" },
|
|
226
|
+
{ name = "filelock" },
|
|
227
|
+
{ name = "platformdirs" },
|
|
228
|
+
{ name = "python-discovery" },
|
|
229
|
+
]
|
|
230
|
+
sdist = { url = "https://files.pythonhosted.org/packages/45/9d/5acd348310e0803c658c8cf7c4d928e2d22fc4f79c29098b651cd3edfdba/virtualenv-21.7.10.tar.gz", hash = "sha256:a7bf10f37ecc36f1942d6e469d6b59f5fe308f60ac711f66f51ef3bd8cb2c9aa", size = 5350247, upload-time = "2026-09-15T23:36:05.739Z" }
|
|
231
|
+
wheels = [
|
|
232
|
+
{ url = "https://files.pythonhosted.org/packages/85/7c/b75957b57ef372d84628b1099c4a530b3c361878cc6c54a8dfc5071d371a/virtualenv-21.7.10-py3-none-any.whl", hash = "sha256:d7ac9669ba19e675ffadbb97fe8e887ef92fb1743fe9a0032be62b947657327a", size = 5324900, upload-time = "2026-09-15T23:36:03.751Z" },
|
|
233
|
+
]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|