minja 1.0.0__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.
@@ -0,0 +1,42 @@
1
+ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3
+
4
+ name: Python package
5
+
6
+ on:
7
+ push:
8
+ branches: [ "main" ]
9
+ pull_request:
10
+ branches: [ "main" ]
11
+
12
+ jobs:
13
+ build:
14
+
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["pypy3.9", "pypy3.10", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v5
26
+
27
+ - name: Set up Python ${{ matrix.python-version }}
28
+ uses: actions/setup-python@v5
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+ allow-prereleases: true
32
+
33
+ - name: Install Dev Tools
34
+ run: |
35
+ uv sync --dev
36
+
37
+ - name: Static Analysis
38
+ run: uv run ruff check .
39
+
40
+ - name: Run Tests
41
+ run: |
42
+ uv run pytest -v
@@ -0,0 +1,37 @@
1
+ # This workflow will upload a Python Package using Twine when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3
+
4
+ # This workflow uses actions that are not certified by GitHub.
5
+ # They are provided by a third-party and are governed by
6
+ # separate terms of service, privacy policy, and support
7
+ # documentation.
8
+
9
+ name: Upload Python Package
10
+
11
+ on:
12
+ release:
13
+ types: [published]
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ deploy:
20
+
21
+ runs-on: ubuntu-latest
22
+
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v5
27
+ with:
28
+ python-version: '3.x'
29
+ - name: Install Hatch
30
+ uses: pypa/hatch@install
31
+ - name: Build using hatch
32
+ run: hatch build
33
+ - name: Publish package
34
+ uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
35
+ with:
36
+ user: __token__
37
+ password: ${{ secrets.PYPI_API_TOKEN }}
minja-1.0.0/.gitignore ADDED
@@ -0,0 +1,171 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # PyPI configuration file
171
+ .pypirc
@@ -0,0 +1 @@
1
+ 3.12
minja-1.0.0/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2025, Hai Vu
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
minja-1.0.0/Makefile ADDED
@@ -0,0 +1,23 @@
1
+ .PHONY: all test run lint clean
2
+
3
+ ### Default target(s)
4
+ all: test
5
+
6
+ ### Perform static analysis
7
+ lint:
8
+ uv tool run ruff check --select I --fix .
9
+ uv tool run ruff format .
10
+ uv tool run ruff check . --fix
11
+
12
+ ### Run unit tests
13
+ test: lint
14
+ uv run pytest -s -v
15
+
16
+ ### Clean up generated files
17
+ clean:
18
+ uv clean
19
+ rm -fr .ruff_cache .venv
20
+
21
+ ### Install this tool locally
22
+ install:
23
+ uv tool install --upgrade .
minja-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: minja
3
+ Version: 1.0.0
4
+ Summary: A Mini Jinja Template Engine
5
+ Author-email: Hai Vu <haivu2004@gmail.com>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+
10
+ # minja
11
+ A Mini Jinja Template Engine
12
+
13
+ I am creating this package because I need something like Jinja, but in
14
+ a much smaller footprint.
15
+
16
+ # Install
17
+
18
+ pip install minja
19
+
20
+ # Usage
21
+
22
+ ## A Simple Usage
23
+
24
+ ```python
25
+ from minja import Template
26
+
27
+ template = Template("{{ flowers }} are {{ color }}")
28
+ print(template.render(flowers="Roses", color="red"))
29
+ # Roses are red
30
+ ```
31
+
32
+ ## What Is a Template?
33
+
34
+ A template is a string embedded variables. For example, the template in
35
+ previous example has two variables, `flowers` and `color`.
36
+
37
+ Note that the spaces surrounding the variables are insignificant: they
38
+ are there to improve readability. That means the following will render
39
+ to the same text:
40
+
41
+ ```python
42
+ t1 = Template(">{{name}}<")
43
+ t2 = Template(">{{ name }}<")
44
+ t3 = Template(">{{ name }}<")
45
+
46
+ print(t1.render(name="Anna")) # '>Anna<'
47
+ print(t2.render(name="Anna")) # '>Anna<'
48
+ print(t3.render(name="Anna")) # '>Anna<'
49
+ ```
50
+
51
+ ## How Can I Get the Names of the Variables?
52
+
53
+ A template maintains a property called `.names` which keep track of the
54
+ names of the variables.
55
+
56
+ ```python
57
+ template = Template("{{ flowers }} are {{ color }}")
58
+ print(template.names) # {'flowers', 'color'}
59
+ ```
60
+
minja-1.0.0/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # minja
2
+ A Mini Jinja Template Engine
3
+
4
+ I am creating this package because I need something like Jinja, but in
5
+ a much smaller footprint.
6
+
7
+ # Install
8
+
9
+ pip install minja
10
+
11
+ # Usage
12
+
13
+ ## A Simple Usage
14
+
15
+ ```python
16
+ from minja import Template
17
+
18
+ template = Template("{{ flowers }} are {{ color }}")
19
+ print(template.render(flowers="Roses", color="red"))
20
+ # Roses are red
21
+ ```
22
+
23
+ ## What Is a Template?
24
+
25
+ A template is a string embedded variables. For example, the template in
26
+ previous example has two variables, `flowers` and `color`.
27
+
28
+ Note that the spaces surrounding the variables are insignificant: they
29
+ are there to improve readability. That means the following will render
30
+ to the same text:
31
+
32
+ ```python
33
+ t1 = Template(">{{name}}<")
34
+ t2 = Template(">{{ name }}<")
35
+ t3 = Template(">{{ name }}<")
36
+
37
+ print(t1.render(name="Anna")) # '>Anna<'
38
+ print(t2.render(name="Anna")) # '>Anna<'
39
+ print(t3.render(name="Anna")) # '>Anna<'
40
+ ```
41
+
42
+ ## How Can I Get the Names of the Variables?
43
+
44
+ A template maintains a property called `.names` which keep track of the
45
+ names of the variables.
46
+
47
+ ```python
48
+ template = Template("{{ flowers }} are {{ color }}")
49
+ print(template.names) # {'flowers', 'color'}
50
+ ```
51
+
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "minja"
3
+ version = "1.0.0"
4
+ description = "A Mini Jinja Template Engine"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Hai Vu", email = "haivu2004@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = []
11
+
12
+ [build-system]
13
+ requires = ["hatchling"]
14
+ build-backend = "hatchling.build"
15
+
16
+ [dependency-groups]
17
+ dev = [
18
+ "pytest>=8.3.4",
19
+ "ruff>=0.9.5",
20
+ ]
@@ -0,0 +1,3 @@
1
+ from .minja import Template
2
+
3
+ __all__ = ["Template"]
@@ -0,0 +1,47 @@
1
+ """
2
+ Implement a simple template rendering because the default string.Template in
3
+ Python versions prior to 3.11 is did not provide a way to get the names of the
4
+ variables. The name `minja` means to be a mini jinja engine.
5
+ """
6
+
7
+ import re
8
+ from typing import Optional
9
+
10
+ __all__ = ["Template"]
11
+
12
+
13
+ NAME_PATTERN = re.compile(
14
+ r"""
15
+ {{ # openning double braces
16
+ \s* # any number of white spaces
17
+ ( # begin group
18
+ [a-zA-Z_] # first char of variable
19
+ [a-zA-Z0-9_]* # subsequent chars
20
+ ) # end group
21
+ \s* # any number of white spaces
22
+ }} # closing double braces
23
+ """,
24
+ re.VERBOSE,
25
+ )
26
+
27
+
28
+ def _create_replace_function(mapping: dict):
29
+ def replace(match: re.Match):
30
+ key = match[1]
31
+ return str(mapping[key])
32
+
33
+ return replace
34
+
35
+
36
+ class Template:
37
+ """A simple Jinja-like template rendering class."""
38
+
39
+ def __init__(self, text: Optional[str] = ""):
40
+ self.text = text
41
+
42
+ @property
43
+ def names(self):
44
+ return set(NAME_PATTERN.findall(self.text))
45
+
46
+ def render(self, **kwargs):
47
+ return NAME_PATTERN.sub(_create_replace_function(kwargs), self.text)
@@ -0,0 +1,16 @@
1
+ """
2
+ Simple usage
3
+ """
4
+
5
+ from minja import Template
6
+
7
+
8
+ def main():
9
+ """Entry"""
10
+ template = Template("{{ flowers }} are {{ color }}")
11
+ print(template.render(flowers="Roses", color="red"))
12
+ # Roses are red
13
+
14
+
15
+ if __name__ == "__main__":
16
+ main()
@@ -0,0 +1,83 @@
1
+ """
2
+ Test the Template class.
3
+ """
4
+
5
+ import pytest
6
+
7
+ from minja import Template
8
+
9
+
10
+ @pytest.fixture
11
+ def template_text():
12
+ return "{{ flowers }} are {{ color }}"
13
+
14
+
15
+ @pytest.fixture
16
+ def flowers_template(template_text):
17
+ return Template(template_text)
18
+
19
+
20
+ def test_simple(flowers_template):
21
+ assert flowers_template.render(color="red", flowers="Roses") == "Roses are red"
22
+
23
+
24
+ def test_load_text_should_set_names(flowers_template):
25
+ assert flowers_template.names == {"flowers", "color"}
26
+
27
+
28
+ def test_no_duplicate_names():
29
+ """Names should be a set, hence no duplicates."""
30
+ template = Template("{{ a }}, {{ b }}, {{ a }}")
31
+ assert template.names == {"a", "b"}
32
+
33
+
34
+ def test_ignore_spaces_inside_braces():
35
+ """Ensure {{foo}} and {{ foo }} are the same."""
36
+ template = Template("{{foo}},{{ foo }}")
37
+ assert template.render(foo="bar") == "bar,bar"
38
+
39
+
40
+ def test_single_char():
41
+ """Single-char variable such as {{a}}, {{b}}."""
42
+ template = Template("Hello, {{w}}")
43
+ assert template.render(w="world") == "Hello, world"
44
+
45
+
46
+ def test_render_without_key(flowers_template):
47
+ with pytest.raises(KeyError, match="flowers"):
48
+ flowers_template.render()
49
+
50
+
51
+ def test_template_without_braces():
52
+ """Test a template which has no braces."""
53
+ template = Template("Hello, world")
54
+ assert template.render() == "Hello, world"
55
+
56
+
57
+ def test_render_multiple_times(flowers_template):
58
+ """Ensure that expansion works many time."""
59
+ assert flowers_template.render(flowers="Roses", color="red") == "Roses are red"
60
+
61
+ with pytest.raises(KeyError, match="color"):
62
+ flowers_template.render(flowers="Violet")
63
+
64
+
65
+ def test_empty_braces():
66
+ """Test empty braces."""
67
+ template = Template("{{}}")
68
+ assert template.render(foo="bar") == "{{}}"
69
+
70
+
71
+ def test_render_non_string():
72
+ """Render other types of data, not just string."""
73
+ template = Template("{{bool_var}}, {{int_var}}")
74
+ assert template.render(bool_var=True, int_var=19) == "True, 19"
75
+
76
+
77
+ def test_update_text():
78
+ """Update text and the names should be updated as well."""
79
+ template = Template("Hello {{ person }}")
80
+ assert template.names == {"person"}
81
+
82
+ template.text = "{{ flowers }} are {{ color }}"
83
+ assert template.names == {"flowers", "color"}
minja-1.0.0/uv.lock ADDED
@@ -0,0 +1,97 @@
1
+ version = 1
2
+ requires-python = ">=3.12"
3
+
4
+ [[package]]
5
+ name = "colorama"
6
+ version = "0.4.6"
7
+ source = { registry = "https://pypi.org/simple" }
8
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
9
+ wheels = [
10
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
11
+ ]
12
+
13
+ [[package]]
14
+ name = "iniconfig"
15
+ version = "2.0.0"
16
+ source = { registry = "https://pypi.org/simple" }
17
+ sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
18
+ wheels = [
19
+ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
20
+ ]
21
+
22
+ [[package]]
23
+ name = "minja"
24
+ version = "1.0.0"
25
+ source = { editable = "." }
26
+
27
+ [package.dev-dependencies]
28
+ dev = [
29
+ { name = "pytest" },
30
+ { name = "ruff" },
31
+ ]
32
+
33
+ [package.metadata]
34
+
35
+ [package.metadata.requires-dev]
36
+ dev = [
37
+ { name = "pytest", specifier = ">=8.3.4" },
38
+ { name = "ruff", specifier = ">=0.9.5" },
39
+ ]
40
+
41
+ [[package]]
42
+ name = "packaging"
43
+ version = "24.2"
44
+ source = { registry = "https://pypi.org/simple" }
45
+ sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
46
+ wheels = [
47
+ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
48
+ ]
49
+
50
+ [[package]]
51
+ name = "pluggy"
52
+ version = "1.5.0"
53
+ source = { registry = "https://pypi.org/simple" }
54
+ sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
55
+ wheels = [
56
+ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
57
+ ]
58
+
59
+ [[package]]
60
+ name = "pytest"
61
+ version = "8.3.4"
62
+ source = { registry = "https://pypi.org/simple" }
63
+ dependencies = [
64
+ { name = "colorama", marker = "sys_platform == 'win32'" },
65
+ { name = "iniconfig" },
66
+ { name = "packaging" },
67
+ { name = "pluggy" },
68
+ ]
69
+ sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 }
70
+ wheels = [
71
+ { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 },
72
+ ]
73
+
74
+ [[package]]
75
+ name = "ruff"
76
+ version = "0.9.5"
77
+ source = { registry = "https://pypi.org/simple" }
78
+ sdist = { url = "https://files.pythonhosted.org/packages/02/74/6c359f6b9ed85b88df6ef31febce18faeb852f6c9855651dfb1184a46845/ruff-0.9.5.tar.gz", hash = "sha256:11aecd7a633932875ab3cb05a484c99970b9d52606ce9ea912b690b02653d56c", size = 3634177 }
79
+ wheels = [
80
+ { url = "https://files.pythonhosted.org/packages/17/4b/82b7c9ac874e72b82b19fd7eab57d122e2df44d2478d90825854f9232d02/ruff-0.9.5-py3-none-linux_armv6l.whl", hash = "sha256:d466d2abc05f39018d53f681fa1c0ffe9570e6d73cde1b65d23bb557c846f442", size = 11681264 },
81
+ { url = "https://files.pythonhosted.org/packages/27/5c/f5ae0a9564e04108c132e1139d60491c0abc621397fe79a50b3dc0bd704b/ruff-0.9.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38840dbcef63948657fa7605ca363194d2fe8c26ce8f9ae12eee7f098c85ac8a", size = 11657554 },
82
+ { url = "https://files.pythonhosted.org/packages/2a/83/c6926fa3ccb97cdb3c438bb56a490b395770c750bf59f9bc1fe57ae88264/ruff-0.9.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d56ba06da53536b575fbd2b56517f6f95774ff7be0f62c80b9e67430391eeb36", size = 11088959 },
83
+ { url = "https://files.pythonhosted.org/packages/af/a7/42d1832b752fe969ffdbfcb1b4cb477cb271bed5835110fb0a16ef31ab81/ruff-0.9.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7cb2a01da08244c50b20ccfaeb5972e4228c3c3a1989d3ece2bc4b1f996001", size = 11902041 },
84
+ { url = "https://files.pythonhosted.org/packages/53/cf/1fffa09fb518d646f560ccfba59f91b23c731e461d6a4dedd21a393a1ff1/ruff-0.9.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96d5c76358419bc63a671caac70c18732d4fd0341646ecd01641ddda5c39ca0b", size = 11421069 },
85
+ { url = "https://files.pythonhosted.org/packages/09/27/bb8f1b7304e2a9431f631ae7eadc35550fe0cf620a2a6a0fc4aa3d736f94/ruff-0.9.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:deb8304636ed394211f3a6d46c0e7d9535b016f53adaa8340139859b2359a070", size = 12625095 },
86
+ { url = "https://files.pythonhosted.org/packages/d7/ce/ab00bc9d3df35a5f1b64f5117458160a009f93ae5caf65894ebb63a1842d/ruff-0.9.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df455000bf59e62b3e8c7ba5ed88a4a2bc64896f900f311dc23ff2dc38156440", size = 13257797 },
87
+ { url = "https://files.pythonhosted.org/packages/88/81/c639a082ae6d8392bc52256058ec60f493c6a4d06d5505bccface3767e61/ruff-0.9.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de92170dfa50c32a2b8206a647949590e752aca8100a0f6b8cefa02ae29dce80", size = 12763793 },
88
+ { url = "https://files.pythonhosted.org/packages/b3/d0/0a3d8f56d1e49af466dc770eeec5c125977ba9479af92e484b5b0251ce9c/ruff-0.9.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d28532d73b1f3f627ba88e1456f50748b37f3a345d2be76e4c653bec6c3e393", size = 14386234 },
89
+ { url = "https://files.pythonhosted.org/packages/04/70/e59c192a3ad476355e7f45fb3a87326f5219cc7c472e6b040c6c6595c8f0/ruff-0.9.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c746d7d1df64f31d90503ece5cc34d7007c06751a7a3bbeee10e5f2463d52d2", size = 12437505 },
90
+ { url = "https://files.pythonhosted.org/packages/55/4e/3abba60a259d79c391713e7a6ccabf7e2c96e5e0a19100bc4204f1a43a51/ruff-0.9.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:11417521d6f2d121fda376f0d2169fb529976c544d653d1d6044f4c5562516ee", size = 11884799 },
91
+ { url = "https://files.pythonhosted.org/packages/a3/db/b0183a01a9f25b4efcae919c18fb41d32f985676c917008620ad692b9d5f/ruff-0.9.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b9d71c3879eb32de700f2f6fac3d46566f644a91d3130119a6378f9312a38e1", size = 11527411 },
92
+ { url = "https://files.pythonhosted.org/packages/0a/e4/3ebfcebca3dff1559a74c6becff76e0b64689cea02b7aab15b8b32ea245d/ruff-0.9.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2e36c61145e70febcb78483903c43444c6b9d40f6d2f800b5552fec6e4a7bb9a", size = 12078868 },
93
+ { url = "https://files.pythonhosted.org/packages/ec/b2/5ab808833e06c0a1b0d046a51c06ec5687b73c78b116e8d77687dc0cd515/ruff-0.9.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2f71d09aeba026c922aa7aa19a08d7bd27c867aedb2f74285a2639644c1c12f5", size = 12524374 },
94
+ { url = "https://files.pythonhosted.org/packages/e0/51/1432afcc3b7aa6586c480142caae5323d59750925c3559688f2a9867343f/ruff-0.9.5-py3-none-win32.whl", hash = "sha256:134f958d52aa6fdec3b294b8ebe2320a950d10c041473c4316d2e7d7c2544723", size = 9853682 },
95
+ { url = "https://files.pythonhosted.org/packages/b7/ad/c7a900591bd152bb47fc4882a27654ea55c7973e6d5d6396298ad3fd6638/ruff-0.9.5-py3-none-win_amd64.whl", hash = "sha256:78cc6067f6d80b6745b67498fb84e87d32c6fc34992b52bffefbdae3442967d6", size = 10865744 },
96
+ { url = "https://files.pythonhosted.org/packages/75/d9/fde7610abd53c0c76b6af72fc679cb377b27c617ba704e25da834e0a0608/ruff-0.9.5-py3-none-win_arm64.whl", hash = "sha256:18a29f1a005bddb229e580795627d297dfa99f16b30c7039e73278cf6b5f9fa9", size = 10064595 },
97
+ ]