├── .github
└── workflows
│ ├── ci.yaml
│ └── release.yaml
├── .gitignore
├── LICENSE
├── README.md
├── autobot
├── __init__.py
├── __main__.py
├── api.py
├── main.py
├── prompt.py
├── py.typed
├── refactor
│ ├── __init__.py
│ ├── patches.py
│ └── refactor.py
├── review
│ ├── __init__.py
│ └── review.py
├── schematic.py
├── schematics
│ ├── assert_equals
│ │ ├── after.py
│ │ └── before.py
│ ├── convert_to_dataclass
│ │ ├── after.py
│ │ └── before.py
│ ├── keyword_only_arguments
│ │ ├── after.py
│ │ └── before.py
│ ├── numpy_builtin_aliases
│ │ ├── after.py
│ │ └── before.py
│ ├── print_statement
│ │ ├── after.py
│ │ └── before.py
│ ├── sorted_attributes
│ │ ├── after.py
│ │ └── before.py
│ ├── standard_library_generics
│ │ ├── after.py
│ │ └── before.py
│ ├── unittest_to_pytest
│ │ ├── after.py
│ │ └── before.py
│ ├── unnecessary_f_strings
│ │ ├── after.py
│ │ └── before.py
│ ├── use_generator
│ │ ├── after.py
│ │ └── before.py
│ └── useless_object_inheritance
│ │ ├── after.py
│ │ └── before.py
├── snippet.py
├── transforms.py
├── utils
│ ├── __init__.py
│ ├── cache.py
│ ├── filesystem.py
│ └── getch.py
└── version.py
├── mypy.ini
├── pyproject.toml
├── tests
├── __init__.py
├── integration
│ ├── __init__.py
│ └── test_prompt.py
└── unit
│ ├── __init__.py
│ └── test_schematic.py
└── uv.lock
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 | name: Build
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - name: Set up Python 3.12
16 | uses: actions/setup-python@v4
17 | with:
18 | python-version: "3.12"
19 | - uses: astral-sh/setup-uv@v1
20 | with:
21 | version: "latest"
22 | - name: Install dependencies
23 | run: uv sync -p 3.12
24 | - name: Mypy
25 | run: uv run mypy autobot tests
26 | - name: unittest
27 | run: uv run python -m unittest discover -s tests/unit
28 | - name: lint
29 | run: uv run ruff check autobot tests
30 | - name: format
31 | run: uv run ruff format --check autobot tests
32 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 | create:
7 | tags:
8 | - v*
9 |
10 | jobs:
11 | build:
12 | name: Build
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: Set up Python 3.12
17 | uses: actions/setup-python@v4
18 | with:
19 | python-version: "3.12"
20 | - uses: astral-sh/setup-uv@v1
21 | with:
22 | version: "latest"
23 | - name: Install dependencies
24 | run: uv sync -p 3.12
25 | - name: Build wheels
26 | run: uv tool run hatch build
27 | - name: Upload wheels
28 | uses: actions/upload-artifact@v2
29 | with:
30 | name: wheels
31 | path: dist
32 |
33 | release:
34 | name: Release
35 | runs-on: ubuntu-latest
36 | needs:
37 | - build
38 | if: "startsWith(github.ref, 'refs/tags/')"
39 | steps:
40 | - uses: actions/download-artifact@v2
41 | with:
42 | name: wheels
43 | - name: Set up Python 3.12
44 | uses: actions/setup-python@v4
45 | with:
46 | python-version: "3.12"
47 | - uses: hynek/setup-cached-uv@v1
48 | - name: Publish to PyPI
49 | env:
50 | TWINE_USERNAME: __token__
51 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
52 | run: |
53 | uv tool run twine upload --skip-existing *
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Local ignores
2 | .autobot_cache
3 | .autobot_patches
4 | autobot/schematics/**/targets
5 |
6 | # Byte-compiled / optimized / DLL files
7 | __pycache__/
8 | *.py[cod]
9 | *$py.class
10 |
11 | # C extensions
12 | *.so
13 |
14 | # Distribution / packaging
15 | .Python
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | wheels/
28 | share/python-wheels/
29 | *.egg-info/
30 | .installed.cfg
31 | *.egg
32 | MANIFEST
33 |
34 | # PyInstaller
35 | # Usually these files are written by a python script from a template
36 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
37 | *.manifest
38 | *.spec
39 |
40 | # Installer logs
41 | pip-log.txt
42 | pip-delete-this-directory.txt
43 |
44 | # Unit test / coverage reports
45 | htmlcov/
46 | .tox/
47 | .nox/
48 | .coverage
49 | .coverage.*
50 | .cache
51 | nosetests.xml
52 | coverage.xml
53 | *.cover
54 | *.py,cover
55 | .hypothesis/
56 | .pytest_cache/
57 | cover/
58 |
59 | # Translations
60 | *.mo
61 | *.pot
62 |
63 | # Django stuff:
64 | *.log
65 | local_settings.py
66 | db.sqlite3
67 | db.sqlite3-journal
68 |
69 | # Flask stuff:
70 | instance/
71 | .webassets-cache
72 |
73 | # Scrapy stuff:
74 | .scrapy
75 |
76 | # Sphinx documentation
77 | docs/_build/
78 |
79 | # PyBuilder
80 | .pybuilder/
81 | target/
82 |
83 | # Jupyter Notebook
84 | .ipynb_checkpoints
85 |
86 | # IPython
87 | profile_default/
88 | ipython_config.py
89 |
90 | # pyenv
91 | # For a library or package, you might want to ignore these files since the code is
92 | # intended to run in multiple environments; otherwise, check them in:
93 | # .python-version
94 |
95 | # pipenv
96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
99 | # install all needed dependencies.
100 | #Pipfile.lock
101 |
102 | # poetry
103 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
104 | # This is especially recommended for binary packages to ensure reproducibility, and is more
105 | # commonly ignored for libraries.
106 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
107 | #poetry.lock
108 |
109 | # pdm
110 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
111 | #pdm.lock
112 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
113 | # in version control.
114 | # https://pdm.fming.dev/#use-with-ide
115 | .pdm.toml
116 |
117 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
118 | __pypackages__/
119 |
120 | # Celery stuff
121 | celerybeat-schedule
122 | celerybeat.pid
123 |
124 | # SageMath parsed files
125 | *.sage.py
126 |
127 | # Environments
128 | .env
129 | .venv
130 | env/
131 | venv/
132 | ENV/
133 | env.bak/
134 | venv.bak/
135 |
136 | # Spyder project settings
137 | .spyderproject
138 | .spyproject
139 |
140 | # Rope project settings
141 | .ropeproject
142 |
143 | # mkdocs documentation
144 | /site
145 |
146 | # mypy
147 | .mypy_cache/
148 | .dmypy.json
149 | dmypy.json
150 |
151 | # Pyre type checker
152 | .pyre/
153 |
154 | # pytype static type analyzer
155 | .pytype/
156 |
157 | # Cython debug symbols
158 | cython_debug/
159 |
160 | # PyCharm
161 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
162 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
163 | # and can be added to the global gitignore or merged into this file. For a more nuclear
164 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
165 | .idea/
166 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Charles Marsh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # autobot
2 |
3 | [](https://github.com/charliermarsh/autobot/actions)
4 | [](https://badge.fury.io/py/autobot-ml)
5 |
6 | An automated code refactoring tool powered by GPT-3. Like GitHub Copilot, for your existing
7 | codebase.
8 |
9 | Autobot takes an example change as input and generates patches for you to review by scanning your
10 | codebase for similar code blocks and "applying" that change to the existing source code.
11 |
12 |
13 |
14 |
15 |
16 | See more examples on
17 | Twitter, or read the
18 | blog post.
19 |
20 | _N.B. Autobot is a prototype and isn't recommended for use on large codebases. See: ["Limitations"](#Limitations)._
21 |
22 | ## Getting started
23 |
24 | Autobot is available as [`autobot-ml`](https://pypi.org/project/autobot-ml/) on PyPI:
25 |
26 | ```shell
27 | pip install autobot-ml
28 | ```
29 |
30 | Autobot depends on the [OpenAI API](https://openai.com/api/) and, in particular, expects your OpenAI
31 | organization ID and API key to be exposed as the `OPENAI_ORGANIZATION` and `OPENAI_API_KEY`
32 | environment variables, respectively.
33 |
34 | Autobot can also read from a `.env` file:
35 |
36 | ```
37 | OPENAI_ORGANIZATION=${YOUR_OPENAI_ORGANIZATION}
38 | OPENAI_API_KEY=${YOUR_OPENAI_API_KEY}
39 | ```
40 |
41 | From there, you can run any of Autobot's built-in refactors (called "schematics"):
42 |
43 | ```shell
44 | autobot run useless_object_inheritance /path/to/file.py
45 | ```
46 |
47 | ## Example usage
48 |
49 | _TL;DR: Autobot is a command-line tool. To generate patches, use `autobot run`; to review the
50 | generated patches, use `autobot review`._
51 |
52 | Autobot is designed around a two-step workflow.
53 |
54 | In the first step (`autobot run {schematic} {files_to_analyze}`), we point Autobot to (1) the
55 | "schematic" that defines our desired change and (2) the files to which the change should be
56 | applied.
57 |
58 | In the second step (`autobot review`), we review the patches that Autobot generated and, for each
59 | suggested change, either apply it to the codebase or reject the patch entirely.
60 |
61 | Autobot ships with several schematics that you can use out-of-the-box:
62 |
63 | - `assert_equals`
64 | - `convert_to_dataclass`
65 | - `numpy_builtin_aliases`
66 | - `print_statement`
67 | - `sorted_attributes`
68 | - `standard_library_generics`
69 | - `unnecessary_f_strings`
70 | - `use_generator`
71 | - `useless_object_inheritance`
72 |
73 | For example: to remove any usages of NumPy's deprecated `np.int` and associated aliases, we'd first
74 | run `autobot run numpy_builtin_aliases /path/to/file.py`, followed by `autobot review`.
75 |
76 | The `schematic` argument to `autobot run` can either reference a directory within `schematics` (like
77 | `numpy_builtin_aliases`, above) or a path to a user-defined schematic directory on-disk.
78 |
79 | ### Implementing a new refactor ("schematic")
80 |
81 | Every refactor facilitated by Autobot requires a "schematic". Autobot ships with a few schematics
82 | in the `schematics` directory, but it's intended to be used with user-provided schematics.
83 |
84 | A schematic is a directory containing two files:
85 |
86 | 1. `before.py`: A code snippet demonstrating the "before" state of the refactor.
87 | 2. `after.py`: A code snippet demonstrating the "after" state of the refactor.
88 |
89 | Each file is expected to consist of a brief top-level docstring describing the "before" or "after"
90 | state, followed by a single function or class.
91 |
92 | For example: in Python 3, `class Foo(object)` is equivalent to `class Foo`. To automatically remove
93 | those useless object inheritances from our codebase, we'd create a `useless_object_inheritance`
94 | directory, and add the following two files:
95 |
96 | ```python
97 | # before.py
98 | """...with object inheritance."""
99 | class Foo(Bar, object):
100 | def __init__(self, x: int) -> None:
101 | self.x = x
102 |
103 | ```
104 |
105 | ```python
106 | # after.py
107 | """...without object inheritance."""
108 | class Foo(Bar):
109 | def __init__(self, x: int) -> None:
110 | self.x = x
111 |
112 | ```
113 |
114 | We'd then run `autobot run ./useless_object_inheritance /path/to/file/or/directory` to generate
115 | patches, followed by `autobot review` to apply or reject the suggested changes.
116 |
117 | ## Limitations
118 |
119 | 1. Running Autobot consumes OpenAI credits and thus could cost you money. Be careful!
120 | 2. By default, Autobot uses OpenAI's `text-davinci-002` model, though `autobot run` accepts a
121 | `--model` parameter, allowing you to select an alternative OpenAI model. Note, though, that
122 | OpenAI's Codex models are currently in a private beta, so `code-davinci-002` and friends may
123 | error for you.
124 | 4. To speed up execution, Autobot calls out to the OpenAI API in parallel. If you haven't upgraded
125 | to a paid account, you may hit rate-limit errors. You can pass `--nthreads 1` to `autobot run`
126 | to disable multi-threading. Running Autobot over large codebases is not recommended (yet).
127 | 5. Depending on the transform type, Autobot will attempt to generate a patch for every function or
128 | every
129 | class. Any function or class that's "too long" for GPT-3's maximum prompt size will be skipped.
130 | 6. Autobot isn't smart enough to handle nested functions (or nested classes), so nested functions
131 | will likely be processed and appear twice.
132 | 7. Autobot only supports Python code for now. (Autobot relies on parsing the AST to extract relevant
133 | code snippets, so additional languages require extending AST support.)
134 |
135 | ## Roadmap
136 |
137 | 1. **Multi-language support.** Autobot only supports Python code for now. Extending to
138 | multi-language support, at least with the current algorithm, will require supporting additional
139 | AST parsers. The most likely outcome here will either be to leverage [`tree-sitter`](https://github.com/tree-sitter/tree-sitter).
140 | 2. **Supporting large codebases.** What would it take to run Autobot over hundreds of thousands of
141 | lines of code?
142 |
143 | ## License
144 |
145 | MIT
146 |
--------------------------------------------------------------------------------
/autobot/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/charliermarsh/autobot/e42c66659bf97b90ca9ff305a19cc99952d0d43f/autobot/__init__.py
--------------------------------------------------------------------------------
/autobot/__main__.py:
--------------------------------------------------------------------------------
1 | from autobot.main import main
2 |
3 | main()
4 |
--------------------------------------------------------------------------------
/autobot/api.py:
--------------------------------------------------------------------------------
1 | """Interface to the OpenAI API."""
2 |
3 | from __future__ import annotations
4 |
5 | import hashlib
6 | import json
7 | import logging
8 | import os
9 |
10 | import openai
11 |
12 | from autobot.utils import cache
13 |
14 |
15 | def init() -> None:
16 | openai.organization = os.environ["OPENAI_ORGANIZATION"]
17 | openai.api_key = os.environ["OPENAI_API_KEY"]
18 |
19 |
20 | def create_completion(
21 | prompt: str,
22 | max_tokens: int,
23 | *,
24 | temperature: int = 0,
25 | model: str = "text-davinci-002",
26 | stop: str | list[str] | None = None,
27 | ) -> openai.Completion:
28 | request_hash = hashlib.md5(
29 | json.dumps({
30 | "prompt": prompt,
31 | "max_tokens": max_tokens,
32 | "temperature": temperature,
33 | "model": model,
34 | "stop": stop,
35 | }).encode("utf-8")
36 | ).hexdigest()
37 |
38 | if response := cache.get_from_cache(request_hash):
39 | logging.info("Reading response from cache...")
40 | return response
41 |
42 | response = openai.Completion.create(
43 | model=model,
44 | prompt=prompt,
45 | temperature=temperature,
46 | max_tokens=max_tokens,
47 | stop=stop,
48 | )
49 | cache.set_in_cache(request_hash, response)
50 | return response
51 |
--------------------------------------------------------------------------------
/autobot/main.py:
--------------------------------------------------------------------------------
1 | """Entrypoint to the autobot CLI."""
2 |
3 | from __future__ import annotations
4 |
5 | import argparse
6 | import logging
7 | from typing import Any
8 |
9 | from dotenv import load_dotenv
10 | from rich.console import Console
11 | from rich.logging import RichHandler
12 |
13 | from autobot.version import __version__
14 |
15 |
16 | def run(options: Any) -> None:
17 | from autobot import api
18 | from autobot.refactor import run_refactor
19 | from autobot.schematic import Schematic, SchematicDefinitionException
20 | from autobot.utils import filesystem
21 |
22 | api.init()
23 |
24 | model: str = options.model
25 | nthreads: int = options.nthreads
26 | verbose: bool = options.verbose
27 |
28 | logging.basicConfig(
29 | format="%(asctime)s %(levelname)-8s %(message)s",
30 | datefmt="%m-%d %H:%M:%S",
31 | level=logging.INFO if verbose else logging.WARNING,
32 | handlers=[RichHandler()],
33 | )
34 |
35 | console = Console()
36 |
37 | try:
38 | schematic: Schematic = Schematic.from_directory(options.schematic)
39 | except SchematicDefinitionException as error:
40 | console.print(f"[bold red]error[/] {error}")
41 | exit(1)
42 |
43 | targets = filesystem.collect_python_files(options.files)
44 | if not targets:
45 | console.print("[bold red]error[/] No Python files found")
46 | exit(1)
47 |
48 | run_refactor(
49 | schematic=schematic,
50 | targets=targets,
51 | nthreads=nthreads,
52 | model=model,
53 | )
54 |
55 |
56 | def review(options: Any) -> None:
57 | from autobot.review import run_review
58 |
59 | run_review()
60 |
61 |
62 | def main() -> None:
63 | load_dotenv()
64 |
65 | parser = argparse.ArgumentParser(
66 | prog="autobot", description="An automated code refactoring tool."
67 | )
68 | parser.add_argument(
69 | "--version", action="version", version=f"%(prog)s {__version__}"
70 | )
71 | subparsers = parser.add_subparsers()
72 |
73 | # autobot run
74 | parser_run = subparsers.add_parser(
75 | "run",
76 | description="An automated code refactoring tool.",
77 | usage="autobot run [schematic] [files [files ...]]",
78 | )
79 | parser_run.add_argument(
80 | "schematic", type=str, help="Path to the autobot schematic."
81 | )
82 | parser_run.add_argument(
83 | "files", type=str, nargs="+", help="Path to the files to refactor."
84 | )
85 | parser_run.add_argument(
86 | "--model",
87 | type=str,
88 | default="text-davinci-002",
89 | choices=(
90 | "text-davinci-002",
91 | "text-curie-001",
92 | "text-babbage-001",
93 | "text-ada-001",
94 | "code-davinci-002",
95 | "code-cushman-001",
96 | ),
97 | help=(
98 | "The OpenAI model to use when generating completions. "
99 | "(Note: OpenAI's Codex models are currently in private beta.)"
100 | ),
101 | )
102 | parser_run.add_argument(
103 | "--nthreads",
104 | type=int,
105 | default=8,
106 | help="The number of threads to use when generating completions.",
107 | )
108 | parser_run.add_argument(
109 | "--verbose",
110 | action="store_true",
111 | help="Show verbose output.",
112 | )
113 | parser_run.set_defaults(func=run)
114 |
115 | # autobot review
116 | parser_review = subparsers.add_parser(
117 | "review",
118 | description="An automated code refactoring tool.",
119 | usage="autobot review",
120 | )
121 | parser_review.set_defaults(func=review)
122 |
123 | args = parser.parse_args()
124 | if hasattr(args, "func"):
125 | args.func(args)
126 | else:
127 | console = Console()
128 | console.print(
129 | "[bold white]Usage: autobot run \[schematic] \[files \[files ...]]"
130 | )
131 | console.print()
132 | console.print("[bold white]An automated code refactoring tool.")
133 |
--------------------------------------------------------------------------------
/autobot/prompt.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import TYPE_CHECKING, NamedTuple, cast
4 |
5 | from autobot import api
6 |
7 | if TYPE_CHECKING:
8 | from autobot.transforms import TransformType
9 |
10 |
11 | class Prompt(NamedTuple):
12 | text: str
13 | max_tokens: int
14 | stop: str | list[str] | None
15 |
16 |
17 | def make_prompt(
18 | snippet: str,
19 | *,
20 | transform_type: TransformType,
21 | before_text: str,
22 | after_text: str,
23 | before_description: str,
24 | after_description: str,
25 | ) -> Prompt:
26 | """Construct a Prompt object from a source snippet."""
27 | node_name = transform_type.plaintext_name()
28 | return Prompt(
29 | f"""### Python {node_name} {before_description}
30 | {before_text}
31 |
32 | ### The same Python {node_name} {after_description}
33 | {after_text}
34 |
35 | ### Python {node_name} {before_description}
36 | {snippet}
37 | ### End of {node_name}
38 |
39 | ### Now rewrite the Python {node_name} {after_description}
40 | """,
41 | max_tokens=len(snippet) // 2,
42 | stop=f"### End of {node_name}",
43 | )
44 |
45 |
46 | def resolve_prompt(prompt: Prompt, *, model: str = "text-davinci-002") -> str:
47 | """Generate a completion for a prompt."""
48 | response = api.create_completion(
49 | prompt=prompt.text,
50 | max_tokens=prompt.max_tokens,
51 | stop=prompt.stop,
52 | model=model,
53 | temperature=0,
54 | )
55 | for choice in response["choices"]:
56 | return cast(str, choice["text"])
57 | else:
58 | raise Exception("Request failed to generate choices.")
59 |
--------------------------------------------------------------------------------
/autobot/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/charliermarsh/autobot/e42c66659bf97b90ca9ff305a19cc99952d0d43f/autobot/py.typed
--------------------------------------------------------------------------------
/autobot/refactor/__init__.py:
--------------------------------------------------------------------------------
1 | from .refactor import run_refactor
2 |
3 | __all__ = ["run_refactor"]
4 |
--------------------------------------------------------------------------------
/autobot/refactor/patches.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import os
4 | import subprocess
5 |
6 | PATCH_DIR = os.path.join(os.getcwd(), ".autobot_patches")
7 |
8 |
9 | def save(patch: str, *, target: str, lineno: int) -> None:
10 | """Save a patch to disk."""
11 | (target_filename, _) = os.path.splitext(target)
12 | patch_filename = os.path.join(
13 | PATCH_DIR,
14 | f"{target_filename}-{lineno}.patch",
15 | )
16 | os.makedirs(os.path.dirname(patch_filename), exist_ok=True)
17 | with open(patch_filename, "w") as fp:
18 | fp.write(patch)
19 |
20 |
21 | def can_apply(patch_file: str) -> bool:
22 | """Return True if a patch file can be applied to its target."""
23 | result = subprocess.run(
24 | ["git", "apply", "--check", patch_file],
25 | stdout=subprocess.DEVNULL,
26 | stderr=subprocess.DEVNULL,
27 | )
28 | return result.returncode == 0
29 |
30 |
31 | def apply(patch_file: str) -> None:
32 | """Apply a patch file to its target."""
33 | subprocess.check_call(["git", "apply", patch_file])
34 |
--------------------------------------------------------------------------------
/autobot/refactor/refactor.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import difflib
4 | import functools
5 | import logging
6 | import os.path
7 | from multiprocessing.pool import ThreadPool
8 | from typing import TYPE_CHECKING
9 |
10 | from rich.console import Console
11 | from rich.progress import Progress
12 |
13 | from autobot import prompt
14 | from autobot.refactor import patches
15 | from autobot.snippet import Snippet, iter_snippets, recontextualize
16 |
17 | if TYPE_CHECKING:
18 | from autobot.schematic import Schematic
19 |
20 |
21 | def _fix_text(
22 | text: str,
23 | *,
24 | schematic: Schematic,
25 | model: str,
26 | ) -> tuple[str, str]:
27 | """Generate a fix for a piece of source code.
28 |
29 | Returns: a tuple of (input, suggested fix), to play nicely with multiprocessing.
30 | """
31 | return text, prompt.resolve_prompt(
32 | prompt.make_prompt(
33 | text,
34 | transform_type=schematic.transform_type,
35 | before_text=schematic.before_text,
36 | after_text=schematic.after_text,
37 | before_description=schematic.before_description,
38 | after_description=schematic.after_description,
39 | ),
40 | model=model,
41 | )
42 |
43 |
44 | def run_refactor(
45 | *,
46 | schematic: Schematic,
47 | targets: list[str],
48 | nthreads: int,
49 | model: str,
50 | ) -> None:
51 | console = Console()
52 |
53 | console.print(
54 | f"[bold]Running [bold cyan]{schematic.title}[/] based on user-provided example"
55 | )
56 |
57 | console.print(
58 | "-" * len(f"Running {schematic.title} based on user-provided example")
59 | )
60 | schematic.print_diff()
61 | console.print(
62 | "-" * len(f"Running {schematic.title} based on user-provided example")
63 | )
64 | console.print()
65 |
66 | # Deduplicate targets, such that if we need to apply the same fix to a bunch of
67 | # snippets, we only make a single API call.
68 | console.print("[bold]1. Extracting AST nodes...")
69 | filename_to_snippets: dict[str, list[Snippet]] = {}
70 | all_snippet_texts: set[str] = set()
71 | for filename in targets:
72 | with open(filename, "r") as fp:
73 | source_code = fp.read()
74 |
75 | filename_to_snippets[filename] = []
76 | for snippet in iter_snippets(
77 | source_code, schematic.transform_type.ast_node_type()
78 | ):
79 | max_snippet_len = 1600
80 | if len(snippet.text) > max_snippet_len:
81 | logging.warning(
82 | f"Snippet at {filename}:{snippet.lineno} is too long "
83 | f"({len(snippet.text)} > {max_snippet_len}); skipping..."
84 | )
85 | continue
86 | filename_to_snippets[filename].append(snippet)
87 | all_snippet_texts.add(snippet.text)
88 |
89 | # Map from snippet text to suggested fix.
90 | console.print("[bold]2. Generating completions...")
91 | snippet_text_to_completion: dict[str, str] = {}
92 | with Progress(transient=True, console=console) as progress:
93 | task = progress.add_task("", total=len(all_snippet_texts))
94 | with ThreadPool(processes=nthreads) as pool:
95 | for text, completion in pool.imap_unordered(
96 | functools.partial(
97 | _fix_text,
98 | schematic=schematic,
99 | model=model,
100 | ),
101 | all_snippet_texts,
102 | ):
103 | progress.update(task, advance=1)
104 | snippet_text_to_completion[text] = completion
105 |
106 | # Format each suggestion as a patch.
107 | console.print("[bold]3. Constructing patches...")
108 | count: int = 0
109 | for target in filename_to_snippets:
110 | with open(target, "r") as fp:
111 | source = fp.read()
112 |
113 | for text, padding, lineno in filename_to_snippets[target]:
114 | before_text = text
115 | after_text = snippet_text_to_completion[text]
116 | patch: str = ""
117 | for line in difflib.unified_diff(
118 | recontextualize(Snippet(before_text, padding, lineno), source),
119 | recontextualize(Snippet(after_text, padding, lineno), source),
120 | lineterm="",
121 | fromfile=os.path.join("a", target),
122 | tofile=os.path.join("b", target),
123 | ):
124 | # TODO(charlie): Why is this necessary? Without it, blank lines contain
125 | # a single space.
126 | stripped = line.strip()
127 | if len(stripped) == 0:
128 | line = stripped
129 |
130 | patch += line
131 | patch += "\n"
132 |
133 | # Save the patch.
134 | if patch:
135 | patches.save(patch, target=target, lineno=lineno)
136 | count += 1
137 |
138 | console.print()
139 | if count == 0:
140 | console.print("[bold white]✨ Done! No suggestions found.")
141 | elif count == 1:
142 | console.print(f"[bold white]✨ Done! Generated {count} patch.")
143 | else:
144 | console.print(f"[bold white]✨ Done! Generated {count} patches.")
145 |
--------------------------------------------------------------------------------
/autobot/review/__init__.py:
--------------------------------------------------------------------------------
1 | from .review import run_review
2 |
3 | __all__ = ["run_review"]
4 |
--------------------------------------------------------------------------------
/autobot/review/review.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import enum
4 | import os
5 |
6 | from colorama import Fore
7 | from rich.console import Console
8 |
9 | from autobot.refactor import patches
10 | from autobot.utils.getch import getch
11 |
12 |
13 | class Resolution(enum.Enum):
14 | ACCEPT = "a"
15 | REJECT = "r"
16 | SKIP = "s"
17 |
18 | @classmethod
19 | def from_code(cls, code: str) -> Resolution | None:
20 | try:
21 | return cls(code)
22 | except ValueError:
23 | return None
24 |
25 |
26 | def run_review() -> None:
27 | patch_files: list[str] = []
28 | for root, _, filenames in os.walk(patches.PATCH_DIR):
29 | for filename in filenames:
30 | if filename.endswith(".patch"):
31 | patch_files.append(os.path.join(root, filename))
32 |
33 | console = Console()
34 |
35 | patches_by_resolution: dict[Resolution, list[str]] = {
36 | Resolution.ACCEPT: [],
37 | Resolution.REJECT: [],
38 | Resolution.SKIP: [],
39 | }
40 | num_patches = len(patch_files)
41 | for i, patch_file in enumerate(patch_files):
42 | if patches.can_apply(patch_file):
43 | with console.screen(hide_cursor=False):
44 | with open(patch_file, "r") as fp:
45 | contents = fp.read()
46 |
47 | console.print(
48 | f"[bold][white]Reviewing [[yellow]{i + 1}/{num_patches}[/yellow]]"
49 | )
50 | console.print(f"Patch file: [cyan]{os.path.basename(patch_file)}")
51 |
52 | console.print()
53 | for line in contents.splitlines():
54 | stripped = line.strip()
55 | if len(stripped) == 0:
56 | line = stripped
57 | if line.startswith("-"):
58 | print(f"{Fore.RED}{line}{Fore.RESET}")
59 | elif line.startswith("+"):
60 | print(f"{Fore.GREEN}{line}{Fore.RESET}")
61 | else:
62 | print(line)
63 | console.print()
64 |
65 | console.print(" [bold green]a[/] accept [grey46]apply the patch[/]")
66 | console.print(" [bold red]r[/] reject [grey46]reject the patch[/]")
67 | console.print(" [bold yellow]s[/] skip [grey46]skip the patch[/]")
68 |
69 | try:
70 | while (resolution := Resolution(getch())) is None:
71 | pass
72 | except KeyboardInterrupt:
73 | exit(0)
74 |
75 | patches_by_resolution[resolution].append(patch_file)
76 | if resolution == Resolution.ACCEPT:
77 | # Apply the patch.
78 | patches.apply(patch_file)
79 | os.remove(patch_file)
80 | elif resolution == Resolution.REJECT:
81 | # Reject the patch.
82 | os.remove(patch_file)
83 | elif resolution == Resolution.SKIP:
84 | # Do nothing.
85 | pass
86 | else:
87 | raise ValueError(f"Unexpected resolution: {resolution}")
88 |
89 | if num_patches > 0:
90 | if num_patches == 1:
91 | console.print(f"[bold]Done![/] Reviewed {len(patch_files)} patch.")
92 | else:
93 | console.print(f"[bold]Done![/] Reviewed {len(patch_files)} patches.")
94 | for resolution in patches_by_resolution:
95 | if patches_by_resolution[resolution]:
96 | if resolution == Resolution.ACCEPT:
97 | console.print("[green]Accepted:")
98 | elif resolution == Resolution.REJECT:
99 | console.print("[red]Rejected:")
100 | elif resolution == Resolution.SKIP:
101 | console.print("[yellow]Skipped:")
102 | else:
103 | raise ValueError(f"Unexpected resolution: {resolution}")
104 |
105 | for patch_file in patches_by_resolution[resolution]:
106 | print(f" {os.path.relpath(patch_file, patches.PATCH_DIR)}")
107 | else:
108 | console.print("[bold]Done![/] No patches to review.")
109 |
--------------------------------------------------------------------------------
/autobot/schematic.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import ast
4 | import difflib
5 | import os
6 | from typing import NamedTuple
7 |
8 | from autobot.transforms import TransformType
9 |
10 | BEFORE_FILENAME: str = "before.py"
11 | AFTER_FILENAME: str = "after.py"
12 |
13 |
14 | class SchematicDefinitionException(Exception):
15 | pass
16 |
17 |
18 | def extract_transform_type(source_code: str) -> TransformType | None:
19 | for node in ast.walk(ast.parse(source_code)):
20 | for transform_type in TransformType:
21 | if isinstance(node, transform_type.ast_node_type()):
22 | return transform_type
23 | else:
24 | return None
25 |
26 |
27 | def extract_source(source_code: str) -> str | None:
28 | for node in ast.walk(ast.parse(source_code)):
29 | for transform_type in TransformType:
30 | if isinstance(node, transform_type.ast_node_type()):
31 | return ast.get_source_segment(source_code, node)
32 | else:
33 | return None
34 |
35 |
36 | def extract_description(source_code: str) -> str | None:
37 | for node in ast.walk(ast.parse(source_code)):
38 | if isinstance(node, ast.Module):
39 | return ast.get_docstring(node)
40 | else:
41 | return None
42 |
43 |
44 | class Schematic(NamedTuple):
45 | title: str
46 | before_text: str
47 | after_text: str
48 | before_description: str
49 | after_description: str
50 | transform_type: TransformType
51 |
52 | @classmethod
53 | def from_directory(cls, dirname: str) -> Schematic:
54 | """Load a Schematic from a directory."""
55 | dirname = dirname.rstrip("/")
56 | title = os.path.basename(dirname)
57 |
58 | if not os.path.isdir(dirname):
59 | # Fallback: this could be a schematic that ships with autobot (i.e. a path
60 | # relative to ./schematics).
61 | bundled_dirname = os.path.join(
62 | os.path.dirname(__file__), "schematics", dirname
63 | )
64 |
65 | if not os.path.isdir(bundled_dirname):
66 | raise SchematicDefinitionException(f"Directory not found: {dirname}")
67 |
68 | dirname = bundled_dirname
69 |
70 | before_filename = os.path.join(dirname, BEFORE_FILENAME)
71 | if not os.path.isfile(before_filename):
72 | raise SchematicDefinitionException(
73 | f"Unable to find file: {before_filename}"
74 | )
75 |
76 | with open(before_filename, "r") as fp:
77 | source_code = fp.read()
78 | if not (transform_type := extract_transform_type(source_code)):
79 | raise SchematicDefinitionException(
80 | f"Invalid transform type found in: {before_filename}"
81 | )
82 | if not (before_text := extract_source(source_code)):
83 | raise SchematicDefinitionException(
84 | f"No source node found in: {before_filename}"
85 | )
86 | if not (before_description := extract_description(source_code)):
87 | raise SchematicDefinitionException(
88 | f"No description found in: {before_filename}"
89 | )
90 | before_description = before_description.lstrip(".").rstrip(".")
91 |
92 | after_filename = os.path.join(dirname, AFTER_FILENAME)
93 | if not os.path.isfile(after_filename):
94 | raise SchematicDefinitionException(f"Unable to find file: {after_filename}")
95 |
96 | with open(after_filename, "r") as fp:
97 | source_code = fp.read()
98 | if not (after_text := extract_source(source_code)):
99 | raise SchematicDefinitionException(
100 | f"No source node found in: {after_filename}"
101 | )
102 | if not (after_description := extract_description(source_code)):
103 | raise SchematicDefinitionException(
104 | f"No description found in: {after_filename}"
105 | )
106 | after_description = after_description.lstrip(".").rstrip(".")
107 |
108 | return cls(
109 | title=title,
110 | before_text=before_text,
111 | after_text=after_text,
112 | before_description=before_description,
113 | after_description=after_description,
114 | transform_type=transform_type,
115 | )
116 |
117 | def print_diff(self) -> None:
118 | from colorama import Fore
119 |
120 | for line in difflib.unified_diff(
121 | self.before_text.splitlines(),
122 | self.after_text.splitlines(),
123 | lineterm="",
124 | fromfile=os.path.join("a", self.title, BEFORE_FILENAME),
125 | tofile=os.path.join("b", self.title, AFTER_FILENAME),
126 | ):
127 | # TODO(charlie): Why is this necessary? Without it, blank lines contain
128 | # a single space.
129 | if len(line.strip()) == 0:
130 | line = line.strip()
131 |
132 | if line.startswith("-"):
133 | print(f"{Fore.RED}{line}{Fore.RESET}")
134 | elif line.startswith("+"):
135 | print(f"{Fore.GREEN}{line}{Fore.RESET}")
136 | else:
137 | print(line)
138 |
--------------------------------------------------------------------------------
/autobot/schematics/assert_equals/after.py:
--------------------------------------------------------------------------------
1 | """...without self.assertEquals."""
2 |
3 |
4 | class MemoryCacheTest(unittest.TestCase):
5 | def test_roundtrip(self) -> None:
6 | cache = MemoryCache(scope="test_roundtrip")
7 |
8 | expected = uuid.uuid4().hex.encode("utf-8")
9 | cache.set("VALUE", io.BytesIO(expected))
10 | actual = cache.get("VALUE") or io.BytesIO()
11 |
12 | self.assertEqual(expected, actual.getvalue())
13 |
--------------------------------------------------------------------------------
/autobot/schematics/assert_equals/before.py:
--------------------------------------------------------------------------------
1 | """...with self.assertEquals."""
2 |
3 |
4 | class MemoryCacheTest(unittest.TestCase):
5 | def test_roundtrip(self) -> None:
6 | cache = MemoryCache(scope="test_roundtrip")
7 |
8 | expected = uuid.uuid4().hex.encode("utf-8")
9 | cache.set("VALUE", io.BytesIO(expected))
10 | actual = cache.get("VALUE") or io.BytesIO()
11 |
12 | self.assertEquals(expected, actual.getvalue())
13 |
--------------------------------------------------------------------------------
/autobot/schematics/convert_to_dataclass/after.py:
--------------------------------------------------------------------------------
1 | """...as a @dataclass."""
2 |
3 |
4 | @dataclass
5 | class MyClass:
6 | x: int
7 | y: int
8 | z: int
9 |
10 | def __post_init__(self) -> None:
11 | print("Initialized MyClass!")
12 |
--------------------------------------------------------------------------------
/autobot/schematics/convert_to_dataclass/before.py:
--------------------------------------------------------------------------------
1 | """...as a standard class."""
2 |
3 |
4 | class MyClass:
5 | def __init__(self, x: int, y: int, z: int):
6 | self.x = x
7 | self.y = y
8 | self.z = z
9 |
10 | print("Initialized MyClass!")
11 |
--------------------------------------------------------------------------------
/autobot/schematics/keyword_only_arguments/after.py:
--------------------------------------------------------------------------------
1 | """...with keyword-only arguments."""
2 |
3 |
4 | def f(x: int, y: int, *, z: int = 1) -> None:
5 | ...
6 |
--------------------------------------------------------------------------------
/autobot/schematics/keyword_only_arguments/before.py:
--------------------------------------------------------------------------------
1 | """...without keyword-only arguments."""
2 |
3 |
4 | def f(x: int, y: int, z: int = 1) -> None:
5 | ...
6 |
--------------------------------------------------------------------------------
/autobot/schematics/numpy_builtin_aliases/after.py:
--------------------------------------------------------------------------------
1 | """...without NumPy's deprecated aliases (so int instead of np.int)."""
2 |
3 |
4 | def f() -> None:
5 | a = np.array(dtype=int)
6 | b = np.dtype(str)
7 | c = np.dtype(object)
8 | d = float(123)
9 |
--------------------------------------------------------------------------------
/autobot/schematics/numpy_builtin_aliases/before.py:
--------------------------------------------------------------------------------
1 | """...with NumPy's builtin aliases (like np.int)."""
2 |
3 |
4 | def f() -> None:
5 | a = np.array(dtype=np.int)
6 | b = np.dtype(np.unicode)
7 | c = np.dtype(np.object)
8 | d = np.float(123)
9 |
--------------------------------------------------------------------------------
/autobot/schematics/print_statement/after.py:
--------------------------------------------------------------------------------
1 | """...without print statements."""
2 |
3 |
4 | def square(x: int) -> int:
5 | return x * x
6 |
--------------------------------------------------------------------------------
/autobot/schematics/print_statement/before.py:
--------------------------------------------------------------------------------
1 | """...with print statements."""
2 |
3 |
4 | def square(x: int) -> int:
5 | print(f"Computing square of: {x}")
6 | return x * x
7 |
--------------------------------------------------------------------------------
/autobot/schematics/sorted_attributes/after.py:
--------------------------------------------------------------------------------
1 | """...with alphabetically sorted class attributes."""
2 |
3 |
4 | class MyUnsortedConstants:
5 | A = "zzz123"
6 | B = "aaa234"
7 | Daaa = "banana"
8 | cab = "foo bar"
9 | z = "hehehe"
10 |
--------------------------------------------------------------------------------
/autobot/schematics/sorted_attributes/before.py:
--------------------------------------------------------------------------------
1 | """...with unsorted class attributes."""
2 |
3 |
4 | class MyUnsortedConstants:
5 | z = "hehehe"
6 | B = "aaa234"
7 | A = "zzz123"
8 | cab = "foo bar"
9 | Daaa = "banana"
10 |
--------------------------------------------------------------------------------
/autobot/schematics/standard_library_generics/after.py:
--------------------------------------------------------------------------------
1 | """...with standard library generics."""
2 |
3 |
4 | def func(
5 | x: list[int] | None,
6 | y: int | None,
7 | z: int | list[int] | None,
8 | ) -> None:
9 | a: list[int] = []
10 | b: set[int] = set()
11 | c: int | None = 0
12 | d: int | str = 0
13 |
--------------------------------------------------------------------------------
/autobot/schematics/standard_library_generics/before.py:
--------------------------------------------------------------------------------
1 | """...with typing module generics."""
2 |
3 |
4 | def func(
5 | x: Optional[List[int]],
6 | y: Optional[int],
7 | z: Optional[Union[int, List[int]]],
8 | ) -> None:
9 | a: List[int] = []
10 | b: Set[int] = set()
11 | c: Optional[int] = 0
12 | d: Union[int, str] = 0
13 |
--------------------------------------------------------------------------------
/autobot/schematics/unittest_to_pytest/after.py:
--------------------------------------------------------------------------------
1 | """...as a pytest test suite instead."""
2 |
3 |
4 | def test_method() -> None:
5 | expected = 3
6 | actual = 1 + 2
7 |
8 | assert expected == actual
9 |
--------------------------------------------------------------------------------
/autobot/schematics/unittest_to_pytest/before.py:
--------------------------------------------------------------------------------
1 | """...as a unittest.TestCase."""
2 |
3 |
4 | def test_method(self) -> None:
5 | expected = 3
6 | actual = 1 + 2
7 |
8 | self.assertEqual(expected, actual)
9 |
--------------------------------------------------------------------------------
/autobot/schematics/unnecessary_f_strings/after.py:
--------------------------------------------------------------------------------
1 | """...without useless f-string prefixes."""
2 |
3 |
4 | def func() -> None:
5 | a = "world"
6 | b = f"Hello, {a}!"
7 | c = "Hello world"
8 | d = "Hello" + ", " + f"{a}!"
9 |
--------------------------------------------------------------------------------
/autobot/schematics/unnecessary_f_strings/before.py:
--------------------------------------------------------------------------------
1 | """...with useless f-string prefixes."""
2 |
3 |
4 | def func() -> None:
5 | a = f"world"
6 | b = f"Hello, {a}!"
7 | c = f"Hello world"
8 | d = f"Hello" + f", " + f"{a}!"
9 |
--------------------------------------------------------------------------------
/autobot/schematics/use_generator/after.py:
--------------------------------------------------------------------------------
1 | """...as a generator."""
2 |
3 |
4 | def compute_squares(n: int) -> Generator[int, None, None]:
5 | for i in range(n):
6 | yield i * i
7 |
--------------------------------------------------------------------------------
/autobot/schematics/use_generator/before.py:
--------------------------------------------------------------------------------
1 | """...as a list builder."""
2 |
3 |
4 | def compute_squares(n: int) -> list[int]:
5 | squares: list[int] = []
6 | for i in range(n):
7 | squares.append(i * i)
8 | return squares
9 |
--------------------------------------------------------------------------------
/autobot/schematics/useless_object_inheritance/after.py:
--------------------------------------------------------------------------------
1 | """...without object inheritance."""
2 |
3 |
4 | class Foo(Bar):
5 | def __init__(self, x: int) -> None:
6 | self.x = x
7 |
--------------------------------------------------------------------------------
/autobot/schematics/useless_object_inheritance/before.py:
--------------------------------------------------------------------------------
1 | """...with object inheritance."""
2 |
3 |
4 | class Foo(Bar, object):
5 | def __init__(self, x: int) -> None:
6 | self.x = x
7 |
--------------------------------------------------------------------------------
/autobot/snippet.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import ast
4 | import re
5 | from typing import Generator, NamedTuple, Type
6 |
7 |
8 | class Snippet(NamedTuple):
9 | """A snippet extracted from source code."""
10 |
11 | text: str
12 | padding: str
13 | lineno: int
14 |
15 | @classmethod
16 | def from_node(cls, source_code: str, node: ast.AST) -> Snippet:
17 | return decontextualize(source_code, node)
18 |
19 |
20 | def decontextualize(source_code: str, node: ast.AST) -> Snippet:
21 | """Decontextualize a snippet from its originating source code.
22 |
23 | Takes the originating source code as input, along with the node to decontextualize,
24 | and extracts the code as a snippet, removing any indentation.
25 | """
26 | # Extract the source segment.
27 | source_segment = ast.get_source_segment(source_code, node, padded=True)
28 | assert source_segment, "Unable to find source segment."
29 |
30 | # Dedent the code:.
31 | # TODO(charlie): This isn't safe. For example, there could be multi-line strings
32 | # within a function that need this padding.
33 | lines = source_segment.splitlines()
34 | if m := re.match(r"(\s+)", lines[0]):
35 | padding = m.group()
36 | source_segment = "\n".join([line.removeprefix(padding) for line in lines])
37 | else:
38 | padding = ""
39 |
40 | lineno: int = node.lineno # type: ignore[attr-defined]
41 |
42 | return Snippet(source_segment, padding, lineno)
43 |
44 |
45 | def recontextualize(snippet: Snippet, source_code: str) -> list[str]:
46 | """Recontextualize a snippet within its originating source code.
47 |
48 | Takes the originating source code and snippet as input, and outputs the lines of the
49 | source code up to and including the snippet, with the snippet adjusted to match the
50 | indentation of its originating context.
51 | """
52 | lines: list[str] = []
53 |
54 | # Prepend any lines of the originating source code that precede the snippet.
55 | source_lines = source_code.splitlines()
56 | if snippet.lineno > 1:
57 | for i in range(snippet.lineno - 1):
58 | lines.append(source_lines[i])
59 |
60 | # Tack on the snippet itself, with indentation re-applied.
61 | for line in snippet.text.splitlines():
62 | lines.append(snippet.padding + line)
63 |
64 | return lines
65 |
66 |
67 | def iter_snippets(
68 | source_code: str,
69 | node_type: Type[ast.AST] | tuple[Type[ast.AST], ...],
70 | ) -> Generator[Snippet, None, None]:
71 | """Generate all snippets from the provided source code.
72 |
73 | Returns: a tuple of (text to fix, any indentation that was removed from the
74 | snippet, line number in the source file).
75 | """
76 | for node in ast.walk(ast.parse(source_code)):
77 | if isinstance(node, node_type):
78 | yield Snippet.from_node(source_code, node)
79 |
--------------------------------------------------------------------------------
/autobot/transforms.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import ast
4 | import enum
5 | from typing import Type
6 |
7 |
8 | class TransformType(enum.Enum):
9 | CLASS = "Class"
10 | FUNCTION = "Function"
11 |
12 | def plaintext_name(self) -> str:
13 | if self == TransformType.CLASS:
14 | return "class"
15 | if self == TransformType.FUNCTION:
16 | return "function"
17 | raise NotImplementedError(f"Unhandled transform type: {self}")
18 |
19 | def ast_node_type(self) -> Type[ast.AST] | tuple[Type[ast.AST], ...]:
20 | if self == TransformType.CLASS:
21 | return ast.ClassDef
22 | if self == TransformType.FUNCTION:
23 | return ast.FunctionDef, ast.AsyncFunctionDef
24 | raise NotImplementedError(f"Unhandled transform type: {self}")
25 |
--------------------------------------------------------------------------------
/autobot/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/charliermarsh/autobot/e42c66659bf97b90ca9ff305a19cc99952d0d43f/autobot/utils/__init__.py
--------------------------------------------------------------------------------
/autobot/utils/cache.py:
--------------------------------------------------------------------------------
1 | """Filesystem-based cache for storing JSON objects."""
2 |
3 | from __future__ import annotations
4 |
5 | import json
6 | import os
7 | from typing import TypeVar, cast
8 |
9 | CACHE_DIR = os.path.join(os.getcwd(), ".autobot_cache")
10 |
11 | T = TypeVar("T")
12 |
13 |
14 | def cache_filename(key: str) -> str:
15 | return os.path.join(CACHE_DIR, key)
16 |
17 |
18 | def has_in_cache(key: str) -> bool:
19 | os.makedirs(os.path.dirname(cache_filename(key)), exist_ok=True)
20 | return os.path.exists(cache_filename(key))
21 |
22 |
23 | def get_from_cache(key: str) -> T | None:
24 | os.makedirs(os.path.dirname(cache_filename(key)), exist_ok=True)
25 | try:
26 | with open(cache_filename(key), "r") as fp:
27 | return cast(T, json.load(fp))
28 | except FileNotFoundError:
29 | return None
30 |
31 |
32 | def set_in_cache(key: str, value: T) -> None:
33 | os.makedirs(os.path.dirname(cache_filename(key)), exist_ok=True)
34 | with open(cache_filename(key), "w") as fp:
35 | json.dump(value, fp)
36 |
37 |
38 | def delete_from_cache(key: str) -> bool:
39 | os.makedirs(os.path.dirname(cache_filename(key)), exist_ok=True)
40 | try:
41 | os.remove(cache_filename(key))
42 | return True
43 | except FileNotFoundError:
44 | return False
45 |
--------------------------------------------------------------------------------
/autobot/utils/filesystem.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import glob
4 | import os
5 |
6 |
7 | def is_python_file(filename: str) -> bool:
8 | """Return True if a file appears to contain Python source code."""
9 | return filename.endswith(".py") or filename.endswith(".pyi")
10 |
11 |
12 | def collect_python_files(targets: list[str]) -> list[str]:
13 | """Enumerate all Python files in a target."""
14 | collected: set[str] = set()
15 | for target in targets:
16 | for file_or_directory in glob.iglob(target):
17 | if os.path.isdir(file_or_directory):
18 | for root, dirnames, filenames in os.walk(file_or_directory):
19 | for filename in filenames:
20 | if is_python_file(filename):
21 | collected.add(os.path.join(root, filename))
22 | elif os.path.isfile(file_or_directory):
23 | if is_python_file(file_or_directory):
24 | collected.add(file_or_directory)
25 |
26 | return sorted(collected)
27 |
--------------------------------------------------------------------------------
/autobot/utils/getch.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import sys
4 | import termios
5 | import tty
6 |
7 |
8 | def getch() -> str:
9 | """Get a single character from standard input."""
10 | fd = sys.stdin.fileno()
11 | old_settings = termios.tcgetattr(fd)
12 | try:
13 | tty.setcbreak(sys.stdin.fileno())
14 | ch = sys.stdin.read(1)
15 | finally:
16 | termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
17 | return ch
18 |
--------------------------------------------------------------------------------
/autobot/version.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.0.16"
2 |
--------------------------------------------------------------------------------
/mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 | files = autobot, tests
3 | exclude = autobot/schematics
4 |
5 | [mypy-colorama.*]
6 | ignore_missing_imports = True
7 |
8 | [mypy-dotenv.*]
9 | ignore_missing_imports = True
10 |
11 | [mypy-openai.*]
12 | ignore_missing_imports = True
13 |
14 | [mypy-rich.*]
15 | ignore_missing_imports = True
16 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["hatchling"]
3 | build-backend = "hatchling.build"
4 |
5 | [project]
6 | name = "autobot-ml"
7 | version = "0.0.16"
8 | requires-python = ">=3.9"
9 | description = "An automated code refactoring tool powered by GPT-3."
10 | authors = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]
11 | license = "MIT"
12 | readme = "README.md"
13 | repository = "https://github.com/charliermarsh/autobot"
14 | classifiers = [
15 | "Development Status :: 3 - Alpha",
16 | "Environment :: Console",
17 | "Intended Audience :: Developers",
18 | "License :: OSI Approved :: MIT License",
19 | "Operating System :: OS Independent",
20 | "Programming Language :: Python :: 3 :: Only",
21 | "Programming Language :: Python :: 3.10",
22 | "Programming Language :: Python :: 3.8",
23 | "Programming Language :: Python :: 3.9",
24 | "Programming Language :: Python",
25 | "Topic :: Software Development :: Libraries :: Python Modules",
26 | "Topic :: Software Development :: Quality Assurance",
27 | ]
28 | packages = [{ include = "autobot" }]
29 | dependencies = [
30 | "colorama>=0.4.5",
31 | "openai>=0.23.0,<0.24.0",
32 | "python-dotenv>=0.21.0",
33 | "rich>=12.5.1",
34 | ]
35 |
36 | [project.scripts]
37 | autobot = "autobot.main:main"
38 |
39 | [tool.uv]
40 | dev-dependencies = [
41 | "black>=22.8.0",
42 | "isort>=5.10.1",
43 | "mypy>=0.981",
44 | "twine>=4.0.1",
45 | "ruff>=0.0.48",
46 | ]
47 |
48 | [tool.ruff]
49 | preview = true
50 | extend-exclude = ["autobot/schematics"]
51 |
52 | [tool.ruff.lint]
53 | extend-select = ["I"]
54 |
55 | [tool.hatch.build.targets.wheel]
56 | packages = ["autobot"]
57 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/charliermarsh/autobot/e42c66659bf97b90ca9ff305a19cc99952d0d43f/tests/__init__.py
--------------------------------------------------------------------------------
/tests/integration/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/charliermarsh/autobot/e42c66659bf97b90ca9ff305a19cc99952d0d43f/tests/integration/__init__.py
--------------------------------------------------------------------------------
/tests/integration/test_prompt.py:
--------------------------------------------------------------------------------
1 | """Test cases to assess prompt completion.
2 |
3 | Note that these tests rely on the OpenAI API.
4 | """
5 |
6 | from __future__ import annotations
7 |
8 | import unittest
9 |
10 | from dotenv import load_dotenv
11 |
12 | from autobot import api, prompt
13 | from autobot.transforms import TransformType
14 |
15 |
16 | class PromptIntegrationTest(unittest.TestCase):
17 | def setUp(self) -> None:
18 | load_dotenv()
19 | api.init()
20 |
21 | def test_useless_object_inheritance(self) -> None:
22 | before_text = """
23 | class Foo(Bar, object):
24 | def __init__(self, x: int) -> None:
25 | self.x = x
26 | """
27 |
28 | after_text = """
29 | class Foo(Bar):
30 | def __init__(self, x: int) -> None:
31 | self.x = x
32 | """
33 |
34 | snippet = """
35 | class CreateTaskResponse(object):
36 | task_id: str
37 | """
38 |
39 | expected = """
40 | class CreateTaskResponse:
41 | task_id: str
42 | """
43 | actual = prompt.resolve_prompt(
44 | prompt.make_prompt(
45 | snippet.strip(),
46 | transform_type=TransformType.CLASS,
47 | before_text=before_text.strip(),
48 | after_text=after_text.strip(),
49 | before_description="with object inheritance",
50 | after_description="without object inheritance",
51 | )
52 | )
53 |
54 | self.assertEqual(expected.strip(), actual.strip())
55 |
56 | def test_sorted_attributes(self) -> None:
57 | before_text = """
58 | class MyUnsortedConstants:
59 | z = "hehehe"
60 | B = "aaa234"
61 | A = "zzz123"
62 | cab = "foo bar"
63 | Daaa = "banana"
64 | """
65 |
66 | after_text = """
67 | class MyUnsortedConstants:
68 | A = "zzz123"
69 | B = "aaa234"
70 | Daaa = "banana"
71 | cab = "foo bar"
72 | z = "hehehe"
73 | """
74 |
75 | snippet = """
76 | @dataclass
77 | class Circle:
78 | \"\"\"A circle in an image, with all its pixel indices/radius/center.\"\"\"
79 |
80 | # All the row coordinates of pixels in the circle, suitable for logical
81 | # indexing with numpy.
82 | rr: np.ndarray
83 |
84 | # All the column coordinates of pixels in the circle, suitable for logical
85 | # indexing with numpy.
86 | cc: np.ndarray
87 |
88 | # Radius of the circle
89 | radius: int
90 |
91 | # Center of the circle (row, column)
92 | center: Tuple[int, int]
93 | """
94 |
95 | expected = """
96 | @dataclass
97 | class Circle:
98 | \"\"\"A circle in an image, with all its pixel indices/radius/center.\"\"\"
99 |
100 | # All the column coordinates of pixels in the circle, suitable for logical
101 | # indexing with numpy.
102 | cc: np.ndarray
103 |
104 | # Center of the circle (row, column)
105 | center: Tuple[int, int]
106 |
107 | # Radius of the circle
108 | radius: int
109 |
110 | # All the row coordinates of pixels in the circle, suitable for logical
111 | # indexing with numpy.
112 | rr: np.ndarray
113 | """
114 | actual = prompt.resolve_prompt(
115 | prompt.make_prompt(
116 | snippet.strip(),
117 | transform_type=TransformType.CLASS,
118 | before_text=before_text.strip(),
119 | after_text=after_text.strip(),
120 | before_description="with unsorted class attributes",
121 | after_description="with alphabetically sorted class attributes",
122 | )
123 | )
124 |
125 | self.assertEqual(expected.strip(), actual.strip())
126 |
127 | def test_standard_library_generics(self) -> None:
128 | before_text = """
129 | def func(
130 | x: Optional[List[int]],
131 | y: Optional[int],
132 | z: Optional[Union[int, List[int]]],
133 | ) -> None:
134 | a: List[int] = []
135 | b: Set[int] = set()
136 | c: Optional[int] = 0
137 | d: Union[int, str] = 0
138 | """
139 |
140 | after_text = """
141 | def func(
142 | x: list[int] | None,
143 | y: int | None,
144 | z: int | list[int] | None,
145 | ) -> None:
146 | a: list[int] = []
147 | b: set[int] = set()
148 | c: int | None = 0
149 | d: int | str = 0
150 | """
151 |
152 | snippet = """
153 | def compute_squares(n: int) -> List[int]:
154 | x: List[int] = []
155 | for i in range(n):
156 | x.append(i * i)
157 | return x
158 | """
159 |
160 | expected = """
161 | def compute_squares(n: int) -> list[int]:
162 | x: list[int] = []
163 | for i in range(n):
164 | x.append(i * i)
165 | return x
166 | """
167 | actual = prompt.resolve_prompt(
168 | prompt.make_prompt(
169 | snippet.strip(),
170 | transform_type=TransformType.CLASS,
171 | before_text=before_text.strip(),
172 | after_text=after_text.strip(),
173 | before_description="with typing module generics",
174 | after_description="with standard library generics",
175 | )
176 | )
177 |
178 | self.assertEqual(expected.strip(), actual.strip())
179 |
180 | def test_use_generator(self) -> None:
181 | before_text = """
182 | def compute_squares(n: int) -> list[int]:
183 | squares: list[int] = []
184 | for i in range(n):
185 | squares.append(i * i)
186 | return squares
187 | """
188 |
189 | after_text = """
190 | def compute_squares(n: int) -> Generator[int, None, None]:
191 | for i in range(n):
192 | yield i * i
193 | """
194 |
195 | snippet = """
196 | def run(self, parameter_name: str, value: pd.DataFrame) -> list[SpecViolation]:
197 | violations: list[SpecViolation] = []
198 | cols = value.columns if isinstance(self.columns, _All) else self.columns
199 | for col in cols:
200 | if col not in value.columns:
201 | violations.append(
202 | SpecViolation(
203 | parameter_name,
204 | f"column {col} missing from dataframe",
205 | pd.DataFrame,
206 | value,
207 | )
208 | )
209 | elif value[col].isnull().values.any():
210 | violations.append(
211 | SpecViolation(
212 | parameter_name,
213 | f"column {col} had null values",
214 | pd.DataFrame,
215 | value,
216 | )
217 | )
218 | return violations
219 | """
220 |
221 | expected = """
222 | def run(self, parameter_name: str, value: pd.DataFrame) -> Generator[SpecViolation, None, None]:
223 | cols = value.columns if isinstance(self.columns, _All) else self.columns
224 | for col in cols:
225 | if col not in value.columns:
226 | yield SpecViolation(
227 | parameter_name,
228 | f"column {col} missing from dataframe",
229 | pd.DataFrame,
230 | value,
231 | )
232 | elif value[col].isnull().values.any():
233 | yield SpecViolation(
234 | parameter_name,
235 | f"column {col} had null values",
236 | pd.DataFrame,
237 | value,
238 | )
239 | """ # noqa: E501
240 |
241 | actual = prompt.resolve_prompt(
242 | prompt.make_prompt(
243 | snippet.strip(),
244 | transform_type=TransformType.FUNCTION,
245 | before_text=before_text.strip(),
246 | after_text=after_text.strip(),
247 | before_description="as a list builder",
248 | after_description="as a generator",
249 | )
250 | )
251 |
252 | self.assertEqual(expected.strip(), actual.strip())
253 |
254 |
255 | if __name__ == "__main__":
256 | unittest.main()
257 |
--------------------------------------------------------------------------------
/tests/unit/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/charliermarsh/autobot/e42c66659bf97b90ca9ff305a19cc99952d0d43f/tests/unit/__init__.py
--------------------------------------------------------------------------------
/tests/unit/test_schematic.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import os.path
4 | import unittest
5 |
6 | from autobot.schematic import Schematic
7 | from autobot.transforms import TransformType
8 |
9 |
10 | class SchematicTest(unittest.TestCase):
11 | def test_from_directory__class(self) -> None:
12 | expected = Schematic(
13 | title="assert_equals",
14 | before_text="""class MemoryCacheTest(unittest.TestCase):
15 | def test_roundtrip(self) -> None:
16 | cache = MemoryCache(scope="test_roundtrip")
17 |
18 | expected = uuid.uuid4().hex.encode("utf-8")
19 | cache.set("VALUE", io.BytesIO(expected))
20 | actual = cache.get("VALUE") or io.BytesIO()
21 |
22 | self.assertEquals(expected, actual.getvalue())""",
23 | after_text="""class MemoryCacheTest(unittest.TestCase):
24 | def test_roundtrip(self) -> None:
25 | cache = MemoryCache(scope="test_roundtrip")
26 |
27 | expected = uuid.uuid4().hex.encode("utf-8")
28 | cache.set("VALUE", io.BytesIO(expected))
29 | actual = cache.get("VALUE") or io.BytesIO()
30 |
31 | self.assertEqual(expected, actual.getvalue())""",
32 | before_description="with self.assertEquals",
33 | after_description="without self.assertEquals",
34 | transform_type=TransformType.CLASS,
35 | )
36 | actual = Schematic.from_directory(
37 | os.path.join(
38 | os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
39 | "autobot",
40 | "schematics",
41 | "assert_equals",
42 | )
43 | )
44 |
45 | self.assertEqual(expected, actual)
46 |
47 | def test_from_directory__function(self) -> None:
48 | expected = Schematic(
49 | title="numpy_builtin_aliases",
50 | before_text="""def f() -> None:
51 | a = np.array(dtype=np.int)
52 | b = np.dtype(np.unicode)
53 | c = np.dtype(np.object)
54 | d = np.float(123)""",
55 | after_text="""def f() -> None:
56 | a = np.array(dtype=int)
57 | b = np.dtype(str)
58 | c = np.dtype(object)
59 | d = float(123)""",
60 | before_description="with NumPy's builtin aliases (like np.int)",
61 | after_description=(
62 | "without NumPy's deprecated aliases (so int instead of np.int)"
63 | ),
64 | transform_type=TransformType.FUNCTION,
65 | )
66 | actual = Schematic.from_directory(
67 | os.path.join(
68 | os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
69 | "autobot",
70 | "schematics",
71 | "numpy_builtin_aliases",
72 | )
73 | )
74 |
75 | self.assertEqual(expected, actual)
76 |
77 |
78 | if __name__ == "__main__":
79 | unittest.main()
80 |
--------------------------------------------------------------------------------
/uv.lock:
--------------------------------------------------------------------------------
1 | version = 1
2 | requires-python = ">=3.9"
3 | resolution-markers = [
4 | "python_full_version < '3.11'",
5 | "python_full_version == '3.11.*'",
6 | "python_full_version >= '3.12'",
7 | ]
8 |
9 | [[package]]
10 | name = "autobot-ml"
11 | version = "0.0.16"
12 | source = { editable = "." }
13 | dependencies = [
14 | { name = "colorama" },
15 | { name = "openai" },
16 | { name = "python-dotenv" },
17 | { name = "rich" },
18 | ]
19 |
20 | [package.dev-dependencies]
21 | dev = [
22 | { name = "black" },
23 | { name = "isort" },
24 | { name = "mypy" },
25 | { name = "ruff" },
26 | { name = "twine" },
27 | ]
28 |
29 | [package.metadata]
30 | requires-dist = [
31 | { name = "colorama", specifier = ">=0.4.5" },
32 | { name = "openai", specifier = ">=0.23.0,<0.24.0" },
33 | { name = "python-dotenv", specifier = ">=0.21.0" },
34 | { name = "rich", specifier = ">=12.5.1" },
35 | ]
36 |
37 | [package.metadata.requires-dev]
38 | dev = [
39 | { name = "black", specifier = ">=22.8.0" },
40 | { name = "isort", specifier = ">=5.10.1" },
41 | { name = "mypy", specifier = ">=0.981" },
42 | { name = "ruff", specifier = ">=0.0.48" },
43 | { name = "twine", specifier = ">=4.0.1" },
44 | ]
45 |
46 | [[package]]
47 | name = "backports-tarfile"
48 | version = "1.2.0"
49 | source = { registry = "https://pypi.org/simple" }
50 | sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406 }
51 | wheels = [
52 | { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181 },
53 | ]
54 |
55 | [[package]]
56 | name = "black"
57 | version = "24.8.0"
58 | source = { registry = "https://pypi.org/simple" }
59 | dependencies = [
60 | { name = "click" },
61 | { name = "mypy-extensions" },
62 | { name = "packaging" },
63 | { name = "pathspec" },
64 | { name = "platformdirs" },
65 | { name = "tomli", marker = "python_full_version < '3.11'" },
66 | { name = "typing-extensions", marker = "python_full_version < '3.11'" },
67 | ]
68 | sdist = { url = "https://files.pythonhosted.org/packages/04/b0/46fb0d4e00372f4a86a6f8efa3cb193c9f64863615e39010b1477e010578/black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f", size = 644810 }
69 | wheels = [
70 | { url = "https://files.pythonhosted.org/packages/47/6e/74e29edf1fba3887ed7066930a87f698ffdcd52c5dbc263eabb06061672d/black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6", size = 1632092 },
71 | { url = "https://files.pythonhosted.org/packages/ab/49/575cb6c3faee690b05c9d11ee2e8dba8fbd6d6c134496e644c1feb1b47da/black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb", size = 1457529 },
72 | { url = "https://files.pythonhosted.org/packages/7a/b4/d34099e95c437b53d01c4aa37cf93944b233066eb034ccf7897fa4e5f286/black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42", size = 1757443 },
73 | { url = "https://files.pythonhosted.org/packages/87/a0/6d2e4175ef364b8c4b64f8441ba041ed65c63ea1db2720d61494ac711c15/black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a", size = 1418012 },
74 | { url = "https://files.pythonhosted.org/packages/08/a6/0a3aa89de9c283556146dc6dbda20cd63a9c94160a6fbdebaf0918e4a3e1/black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1", size = 1615080 },
75 | { url = "https://files.pythonhosted.org/packages/db/94/b803d810e14588bb297e565821a947c108390a079e21dbdcb9ab6956cd7a/black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af", size = 1438143 },
76 | { url = "https://files.pythonhosted.org/packages/a5/b5/f485e1bbe31f768e2e5210f52ea3f432256201289fd1a3c0afda693776b0/black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4", size = 1738774 },
77 | { url = "https://files.pythonhosted.org/packages/a8/69/a000fc3736f89d1bdc7f4a879f8aaf516fb03613bb51a0154070383d95d9/black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af", size = 1427503 },
78 | { url = "https://files.pythonhosted.org/packages/a2/a8/05fb14195cfef32b7c8d4585a44b7499c2a4b205e1662c427b941ed87054/black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368", size = 1646132 },
79 | { url = "https://files.pythonhosted.org/packages/41/77/8d9ce42673e5cb9988f6df73c1c5c1d4e9e788053cccd7f5fb14ef100982/black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed", size = 1448665 },
80 | { url = "https://files.pythonhosted.org/packages/cc/94/eff1ddad2ce1d3cc26c162b3693043c6b6b575f538f602f26fe846dfdc75/black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018", size = 1762458 },
81 | { url = "https://files.pythonhosted.org/packages/28/ea/18b8d86a9ca19a6942e4e16759b2fa5fc02bbc0eb33c1b866fcd387640ab/black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2", size = 1436109 },
82 | { url = "https://files.pythonhosted.org/packages/13/b2/b3f24fdbb46f0e7ef6238e131f13572ee8279b70f237f221dd168a9dba1a/black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c", size = 1631706 },
83 | { url = "https://files.pythonhosted.org/packages/d9/35/31010981e4a05202a84a3116423970fd1a59d2eda4ac0b3570fbb7029ddc/black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e", size = 1457429 },
84 | { url = "https://files.pythonhosted.org/packages/27/25/3f706b4f044dd569a20a4835c3b733dedea38d83d2ee0beb8178a6d44945/black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47", size = 1756488 },
85 | { url = "https://files.pythonhosted.org/packages/63/72/79375cd8277cbf1c5670914e6bd4c1b15dea2c8f8e906dc21c448d0535f0/black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb", size = 1417721 },
86 | { url = "https://files.pythonhosted.org/packages/27/1e/83fa8a787180e1632c3d831f7e58994d7aaf23a0961320d21e84f922f919/black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed", size = 206504 },
87 | ]
88 |
89 | [[package]]
90 | name = "certifi"
91 | version = "2024.7.4"
92 | source = { registry = "https://pypi.org/simple" }
93 | sdist = { url = "https://files.pythonhosted.org/packages/c2/02/a95f2b11e207f68bc64d7aae9666fed2e2b3f307748d5123dffb72a1bbea/certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", size = 164065 }
94 | wheels = [
95 | { url = "https://files.pythonhosted.org/packages/1c/d5/c84e1a17bf61d4df64ca866a1c9a913874b4e9bdc131ec689a0ad013fb36/certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90", size = 162960 },
96 | ]
97 |
98 | [[package]]
99 | name = "cffi"
100 | version = "1.17.0"
101 | source = { registry = "https://pypi.org/simple" }
102 | dependencies = [
103 | { name = "pycparser" },
104 | ]
105 | sdist = { url = "https://files.pythonhosted.org/packages/1e/bf/82c351342972702867359cfeba5693927efe0a8dd568165490144f554b18/cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76", size = 516073 }
106 | wheels = [
107 | { url = "https://files.pythonhosted.org/packages/00/2a/9071bf1e20bf9f695643b6c3e0f838f340b95ee29de0d1bb7968772409be/cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb", size = 181841 },
108 | { url = "https://files.pythonhosted.org/packages/4b/42/60116f10466d692b64aef32ac40fd79b11344ab6ef889ff8e3d047f2fcb2/cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a", size = 178242 },
109 | { url = "https://files.pythonhosted.org/packages/26/8e/a53f844454595c6e9215e56cda123db3427f8592f2c7b5ef1be782f620d6/cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42", size = 425676 },
110 | { url = "https://files.pythonhosted.org/packages/60/ac/6402563fb40b64c7ccbea87836d9c9498b374629af3449f3d8ff34df187d/cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d", size = 447842 },
111 | { url = "https://files.pythonhosted.org/packages/b2/e7/e2ffdb8de59f48f17b196813e9c717fbed2364e39b10bdb3836504e89486/cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2", size = 455224 },
112 | { url = "https://files.pythonhosted.org/packages/59/55/3e8968e92fe35c1c368959a070a1276c10cae29cdad0fd0daa36c69e237e/cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab", size = 436341 },
113 | { url = "https://files.pythonhosted.org/packages/7f/df/700aaf009dfbfa04acb1ed487586c03c788c6a312f0361ad5f298c5f5a7d/cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b", size = 445861 },
114 | { url = "https://files.pythonhosted.org/packages/5a/70/637f070aae533ea11ab77708a820f3935c0edb4fbcef9393b788e6f426a5/cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206", size = 460982 },
115 | { url = "https://files.pythonhosted.org/packages/f7/1a/7d4740fa1ccc4fcc888963fc3165d69ef1a2c8d42c8911c946703ff5d4a5/cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa", size = 438434 },
116 | { url = "https://files.pythonhosted.org/packages/d0/d9/c48cc38aaf6f53a8b5d2dbf6fe788410fcbab33b15a69c56c01d2b08f6a2/cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f", size = 461219 },
117 | { url = "https://files.pythonhosted.org/packages/26/ec/b6a7f660a7f27bd2bb53fe99a2ccafa279088395ec8639b25b8950985b2d/cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc", size = 171406 },
118 | { url = "https://files.pythonhosted.org/packages/08/42/8c00824787e6f5ec55194f5cd30c4ba4b9d9d5bb0d4d0007b1bb948d4ad4/cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2", size = 180809 },
119 | { url = "https://files.pythonhosted.org/packages/53/cc/9298fb6235522e00e47d78d6aa7f395332ef4e5f6fe124f9a03aa60600f7/cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720", size = 181912 },
120 | { url = "https://files.pythonhosted.org/packages/e7/79/dc5334fbe60635d0846c56597a8d2af078a543ff22bc48d36551a0de62c2/cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9", size = 178297 },
121 | { url = "https://files.pythonhosted.org/packages/39/d7/ef1b6b16b51ccbabaced90ff0d821c6c23567fc4b2e4a445aea25d3ceb92/cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb", size = 444909 },
122 | { url = "https://files.pythonhosted.org/packages/29/b8/6e3c61885537d985c78ef7dd779b68109ba256263d74a2f615c40f44548d/cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424", size = 468854 },
123 | { url = "https://files.pythonhosted.org/packages/0b/49/adad1228e19b931e523c2731e6984717d5f9e33a2f9971794ab42815b29b/cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d", size = 476890 },
124 | { url = "https://files.pythonhosted.org/packages/76/54/c00f075c3e7fd14d9011713bcdb5b4f105ad044c5ad948db7b1a0a7e4e78/cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8", size = 459374 },
125 | { url = "https://files.pythonhosted.org/packages/f3/b9/f163bb3fa4fbc636ee1f2a6a4598c096cdef279823ddfaa5734e556dd206/cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6", size = 466891 },
126 | { url = "https://files.pythonhosted.org/packages/31/52/72bbc95f6d06ff2e88a6fa13786be4043e542cb24748e1351aba864cb0a7/cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91", size = 477658 },
127 | { url = "https://files.pythonhosted.org/packages/67/20/d694811457eeae0c7663fa1a7ca201ce495533b646c1180d4ac25684c69c/cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8", size = 453890 },
128 | { url = "https://files.pythonhosted.org/packages/dc/79/40cbf5739eb4f694833db5a27ce7f63e30a9b25b4a836c4f25fb7272aacc/cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb", size = 478254 },
129 | { url = "https://files.pythonhosted.org/packages/e9/eb/2c384c385cca5cae67ca10ac4ef685277680b8c552b99aedecf4ea23ff7e/cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9", size = 171285 },
130 | { url = "https://files.pythonhosted.org/packages/ca/42/74cb1e0f1b79cb64672f3cb46245b506239c1297a20c0d9c3aeb3929cb0c/cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0", size = 180842 },
131 | { url = "https://files.pythonhosted.org/packages/1a/1f/7862231350cc959a3138889d2c8d33da7042b22e923457dfd4cd487d772a/cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc", size = 182826 },
132 | { url = "https://files.pythonhosted.org/packages/8b/8c/26119bf8b79e05a1c39812064e1ee7981e1f8a5372205ba5698ea4dd958d/cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59", size = 178494 },
133 | { url = "https://files.pythonhosted.org/packages/61/94/4882c47d3ad396d91f0eda6ef16d45be3d752a332663b7361933039ed66a/cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb", size = 454459 },
134 | { url = "https://files.pythonhosted.org/packages/0f/7c/a6beb119ad515058c5ee1829742d96b25b2b9204ff920746f6e13bf574eb/cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195", size = 478502 },
135 | { url = "https://files.pythonhosted.org/packages/61/8a/2575cd01a90e1eca96a30aec4b1ac101a6fae06c49d490ac2704fa9bc8ba/cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e", size = 485381 },
136 | { url = "https://files.pythonhosted.org/packages/cd/66/85899f5a9f152db49646e0c77427173e1b77a1046de0191ab3b0b9a5e6e3/cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828", size = 470907 },
137 | { url = "https://files.pythonhosted.org/packages/00/13/150924609bf377140abe6e934ce0a57f3fc48f1fd956ec1f578ce97a4624/cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150", size = 479074 },
138 | { url = "https://files.pythonhosted.org/packages/17/fd/7d73d7110155c036303b0a6462c56250e9bc2f4119d7591d27417329b4d1/cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a", size = 484225 },
139 | { url = "https://files.pythonhosted.org/packages/fc/83/8353e5c9b01bb46332dac3dfb18e6c597a04ceb085c19c814c2f78a8c0d0/cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885", size = 488388 },
140 | { url = "https://files.pythonhosted.org/packages/73/0c/f9d5ca9a095b1fc88ef77d1f8b85d11151c374144e4606da33874e17b65b/cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492", size = 172096 },
141 | { url = "https://files.pythonhosted.org/packages/72/21/8c5d285fe20a6e31d29325f1287bb0e55f7d93630a5a44cafdafb5922495/cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2", size = 181478 },
142 | { url = "https://files.pythonhosted.org/packages/17/8f/581f2f3c3464d5f7cf87c2f7a5ba9acc6976253e02d73804240964243ec2/cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118", size = 182638 },
143 | { url = "https://files.pythonhosted.org/packages/8d/1c/c9afa66684b7039f48018eb11b229b659dfb32b7a16b88251bac106dd1ff/cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7", size = 178453 },
144 | { url = "https://files.pythonhosted.org/packages/cc/b6/1a134d479d3a5a1ff2fabbee551d1d3f1dd70f453e081b5f70d604aae4c0/cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377", size = 454441 },
145 | { url = "https://files.pythonhosted.org/packages/b1/b4/e1569475d63aad8042b0935dbf62ae2a54d1e9142424e2b0e924d2d4a529/cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb", size = 478543 },
146 | { url = "https://files.pythonhosted.org/packages/d2/40/a9ad03fbd64309dec5bb70bc803a9a6772602de0ee164d7b9a6ca5a89249/cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555", size = 485463 },
147 | { url = "https://files.pythonhosted.org/packages/a6/1a/f10be60e006dd9242a24bcc2b1cd55c34c578380100f742d8c610f7a5d26/cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204", size = 470854 },
148 | { url = "https://files.pythonhosted.org/packages/cc/b3/c035ed21aa3d39432bd749fe331ee90e4bc83ea2dbed1f71c4bc26c41084/cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f", size = 479096 },
149 | { url = "https://files.pythonhosted.org/packages/00/cb/6f7edde01131de9382c89430b8e253b8c8754d66b63a62059663ceafeab2/cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0", size = 484013 },
150 | { url = "https://files.pythonhosted.org/packages/b9/83/8e4e8c211ea940210d293e951bf06b1bfb90f2eeee590e9778e99b4a8676/cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4", size = 488119 },
151 | { url = "https://files.pythonhosted.org/packages/5e/52/3f7cfbc4f444cb4f73ff17b28690d12436dde665f67d68f1e1687908ab6c/cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a", size = 172122 },
152 | { url = "https://files.pythonhosted.org/packages/94/19/cf5baa07ee0f0e55eab7382459fbddaba0fdb0ba45973dd92556ae0d02db/cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7", size = 181504 },
153 | { url = "https://files.pythonhosted.org/packages/96/22/7866bf5450d6a5b8cf4123abde25b2126fce03ac4efc1244a44367b01c65/cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2", size = 181868 },
154 | { url = "https://files.pythonhosted.org/packages/0c/03/934cd50132c1637a52ab41c093ff89b93086181f6cdc40d43185083818c1/cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759", size = 178261 },
155 | { url = "https://files.pythonhosted.org/packages/4a/1e/06c7bc7ed387e42f0ecdef2477a5b291455c2158bb7a565848ef96bba113/cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4", size = 424564 },
156 | { url = "https://files.pythonhosted.org/packages/b7/9b/43f26a558d192bb0691051153add44404af0adf6e3e35d5ce83340d41a92/cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82", size = 446854 },
157 | { url = "https://files.pythonhosted.org/packages/b5/5c/7777c4b0fc212caf180b20ec51da3d9fa00910d40f042004d33679f39ec7/cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf", size = 454217 },
158 | { url = "https://files.pythonhosted.org/packages/8f/90/a40b9821755bd3dfd2dd9a341b660cd57dfa2fc3bb9d8c4499477fa27ae3/cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058", size = 435285 },
159 | { url = "https://files.pythonhosted.org/packages/e1/d3/36e54b85f670400ff0440ab743fa0de66bdd89b8f54b7d2370708cdcb03f/cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932", size = 444868 },
160 | { url = "https://files.pythonhosted.org/packages/15/aa/62f87ceb24b03e42061050b1139864347fd73291d2b70b3daefd0c4fdaa8/cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693", size = 460136 },
161 | { url = "https://files.pythonhosted.org/packages/d4/b6/7abfb922035cc03d2a6c05b6e90f55d60bfea26ef97a2d10357b3f0bdbf3/cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3", size = 437565 },
162 | { url = "https://files.pythonhosted.org/packages/83/a8/306c52a4625eef30a6d7828c0c7ecaf9a11e1fc83efe506d6fcf980b68c7/cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4", size = 460284 },
163 | { url = "https://files.pythonhosted.org/packages/a8/05/4daca3a5d2af2af95828b35e65221d4f8afb6155c9d80a1ebda7a11348ab/cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb", size = 171382 },
164 | { url = "https://files.pythonhosted.org/packages/89/2d/ec3ae32daf8713681ded997aa2e6d68306c11a41627fb351201111ea0d24/cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29", size = 180820 },
165 | ]
166 |
167 | [[package]]
168 | name = "charset-normalizer"
169 | version = "3.3.2"
170 | source = { registry = "https://pypi.org/simple" }
171 | sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 }
172 | wheels = [
173 | { url = "https://files.pythonhosted.org/packages/2b/61/095a0aa1a84d1481998b534177c8566fdc50bb1233ea9a0478cd3cc075bd/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", size = 194219 },
174 | { url = "https://files.pythonhosted.org/packages/cc/94/f7cf5e5134175de79ad2059edf2adce18e0685ebdb9227ff0139975d0e93/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", size = 122521 },
175 | { url = "https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", size = 120383 },
176 | { url = "https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", size = 138223 },
177 | { url = "https://files.pythonhosted.org/packages/05/8c/eb854996d5fef5e4f33ad56927ad053d04dc820e4a3d39023f35cad72617/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", size = 148101 },
178 | { url = "https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", size = 140699 },
179 | { url = "https://files.pythonhosted.org/packages/da/f1/3702ba2a7470666a62fd81c58a4c40be00670e5006a67f4d626e57f013ae/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", size = 142065 },
180 | { url = "https://files.pythonhosted.org/packages/3f/ba/3f5e7be00b215fa10e13d64b1f6237eb6ebea66676a41b2bcdd09fe74323/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", size = 144505 },
181 | { url = "https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", size = 139425 },
182 | { url = "https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", size = 145287 },
183 | { url = "https://files.pythonhosted.org/packages/58/78/a0bc646900994df12e07b4ae5c713f2b3e5998f58b9d3720cce2aa45652f/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", size = 149929 },
184 | { url = "https://files.pythonhosted.org/packages/eb/5c/97d97248af4920bc68687d9c3b3c0f47c910e21a8ff80af4565a576bd2f0/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", size = 141605 },
185 | { url = "https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", size = 142646 },
186 | { url = "https://files.pythonhosted.org/packages/ae/d5/4fecf1d58bedb1340a50f165ba1c7ddc0400252d6832ff619c4568b36cc0/charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", size = 92846 },
187 | { url = "https://files.pythonhosted.org/packages/a2/a0/4af29e22cb5942488cf45630cbdd7cefd908768e69bdd90280842e4e8529/charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", size = 100343 },
188 | { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 },
189 | { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 },
190 | { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 },
191 | { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 },
192 | { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 },
193 | { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 },
194 | { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 },
195 | { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 },
196 | { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 },
197 | { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 },
198 | { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 },
199 | { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 },
200 | { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 },
201 | { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 },
202 | { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 },
203 | { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 },
204 | { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 },
205 | { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 },
206 | { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 },
207 | { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 },
208 | { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 },
209 | { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 },
210 | { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 },
211 | { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 },
212 | { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 },
213 | { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 },
214 | { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 },
215 | { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 },
216 | { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 },
217 | { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 },
218 | { url = "https://files.pythonhosted.org/packages/f7/9d/bcf4a449a438ed6f19790eee543a86a740c77508fbc5ddab210ab3ba3a9a/charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", size = 194198 },
219 | { url = "https://files.pythonhosted.org/packages/66/fe/c7d3da40a66a6bf2920cce0f436fa1f62ee28aaf92f412f0bf3b84c8ad6c/charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", size = 122494 },
220 | { url = "https://files.pythonhosted.org/packages/2a/9d/a6d15bd1e3e2914af5955c8eb15f4071997e7078419328fee93dfd497eb7/charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", size = 120393 },
221 | { url = "https://files.pythonhosted.org/packages/3d/85/5b7416b349609d20611a64718bed383b9251b5a601044550f0c8983b8900/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", size = 138331 },
222 | { url = "https://files.pythonhosted.org/packages/79/66/8946baa705c588521afe10b2d7967300e49380ded089a62d38537264aece/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", size = 148097 },
223 | { url = "https://files.pythonhosted.org/packages/44/80/b339237b4ce635b4af1c73742459eee5f97201bd92b2371c53e11958392e/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", size = 140711 },
224 | { url = "https://files.pythonhosted.org/packages/98/69/5d8751b4b670d623aa7a47bef061d69c279e9f922f6705147983aa76c3ce/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", size = 142251 },
225 | { url = "https://files.pythonhosted.org/packages/1f/8d/33c860a7032da5b93382cbe2873261f81467e7b37f4ed91e25fed62fd49b/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", size = 144636 },
226 | { url = "https://files.pythonhosted.org/packages/c2/65/52aaf47b3dd616c11a19b1052ce7fa6321250a7a0b975f48d8c366733b9f/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", size = 139514 },
227 | { url = "https://files.pythonhosted.org/packages/51/fd/0ee5b1c2860bb3c60236d05b6e4ac240cf702b67471138571dad91bcfed8/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", size = 145528 },
228 | { url = "https://files.pythonhosted.org/packages/e1/9c/60729bf15dc82e3aaf5f71e81686e42e50715a1399770bcde1a9e43d09db/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", size = 149804 },
229 | { url = "https://files.pythonhosted.org/packages/53/cd/aa4b8a4d82eeceb872f83237b2d27e43e637cac9ffaef19a1321c3bafb67/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", size = 141708 },
230 | { url = "https://files.pythonhosted.org/packages/54/7f/cad0b328759630814fcf9d804bfabaf47776816ad4ef2e9938b7e1123d04/charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561", size = 142708 },
231 | { url = "https://files.pythonhosted.org/packages/c1/9d/254a2f1bcb0ce9acad838e94ed05ba71a7cb1e27affaa4d9e1ca3958cdb6/charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", size = 92830 },
232 | { url = "https://files.pythonhosted.org/packages/2f/0e/d7303ccae9735ff8ff01e36705ad6233ad2002962e8668a970fc000c5e1b/charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", size = 100376 },
233 | { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 },
234 | ]
235 |
236 | [[package]]
237 | name = "click"
238 | version = "8.1.7"
239 | source = { registry = "https://pypi.org/simple" }
240 | dependencies = [
241 | { name = "colorama", marker = "platform_system == 'Windows'" },
242 | ]
243 | sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
244 | wheels = [
245 | { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 },
246 | ]
247 |
248 | [[package]]
249 | name = "colorama"
250 | version = "0.4.6"
251 | source = { registry = "https://pypi.org/simple" }
252 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
253 | wheels = [
254 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
255 | ]
256 |
257 | [[package]]
258 | name = "cryptography"
259 | version = "43.0.0"
260 | source = { registry = "https://pypi.org/simple" }
261 | dependencies = [
262 | { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
263 | ]
264 | sdist = { url = "https://files.pythonhosted.org/packages/69/ec/9fb9dcf4f91f0e5e76de597256c43eedefd8423aa59be95c70c4c3db426a/cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e", size = 686873 }
265 | wheels = [
266 | { url = "https://files.pythonhosted.org/packages/d3/46/dcd2eb6840b9452e7fbc52720f3dc54a85eb41e68414733379e8f98e3275/cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74", size = 6239718 },
267 | { url = "https://files.pythonhosted.org/packages/e8/23/b0713319edff1d8633775b354f8b34a476e4dd5f4cd4b91e488baec3361a/cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895", size = 3808466 },
268 | { url = "https://files.pythonhosted.org/packages/77/9d/0b98c73cebfd41e4fb0439fe9ce08022e8d059f51caa7afc8934fc1edcd9/cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22", size = 3998060 },
269 | { url = "https://files.pythonhosted.org/packages/ae/71/e073795d0d1624847f323481f7d84855f699172a632aa37646464b0e1712/cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47", size = 3792596 },
270 | { url = "https://files.pythonhosted.org/packages/83/25/439a8ddd8058e7f898b7d27c36f94b66c8c8a2d60e1855d725845f4be0bc/cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf", size = 4008355 },
271 | { url = "https://files.pythonhosted.org/packages/c7/a2/1607f1295eb2c30fcf2c07d7fd0c3772d21dcdb827de2b2730b02df0af51/cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55", size = 3899133 },
272 | { url = "https://files.pythonhosted.org/packages/5e/64/f41f42ddc9c583737c9df0093affb92c61de7d5b0d299bf644524afe31c1/cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431", size = 4096946 },
273 | { url = "https://files.pythonhosted.org/packages/cd/cd/d165adcf3e707d6a049d44ade6ca89973549bed0ab3686fa49efdeefea53/cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc", size = 2616826 },
274 | { url = "https://files.pythonhosted.org/packages/f9/b7/38924229e84c41b0e88d7a5eed8a29d05a44364f85fbb9ddb3984b746fd2/cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778", size = 3078700 },
275 | { url = "https://files.pythonhosted.org/packages/66/d7/397515233e6a861f921bd0365b162b38e0cc513fcf4f1bdd9cc7bc5a3384/cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66", size = 6242814 },
276 | { url = "https://files.pythonhosted.org/packages/58/aa/99b2c00a4f54c60d210d6d1759c720ecf28305aa32d6fb1bb1853f415be6/cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5", size = 3809467 },
277 | { url = "https://files.pythonhosted.org/packages/76/eb/ab783b47b3b9b55371b4361c7ec695144bde1a3343ff2b7a8c1d8fe617bb/cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e", size = 3998617 },
278 | { url = "https://files.pythonhosted.org/packages/a3/62/62770f34290ebb1b6542bd3f13b3b102875b90aed4804e296f8d2a5ac6d7/cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5", size = 3794003 },
279 | { url = "https://files.pythonhosted.org/packages/0f/6c/b42660b3075ff543065b2c1c5a3d9bedaadcff8ebce2ee981be2babc2934/cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f", size = 4008774 },
280 | { url = "https://files.pythonhosted.org/packages/f7/74/028cea86db9315ba3f991e307adabf9f0aa15067011137c38b2fb2aa16eb/cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0", size = 3900098 },
281 | { url = "https://files.pythonhosted.org/packages/bd/f6/e4387edb55563e2546028ba4c634522fe727693d3cdd9ec0ecacedc75411/cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b", size = 4096867 },
282 | { url = "https://files.pythonhosted.org/packages/ce/61/55560405e75432bdd9f6cf72fa516cab623b83a3f6d230791bc8fc4afeee/cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf", size = 2616481 },
283 | { url = "https://files.pythonhosted.org/packages/e6/3d/696e7a0f04555c58a2813d47aaa78cb5ba863c1f453c74a4f45ae772b054/cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709", size = 3081462 },
284 | { url = "https://files.pythonhosted.org/packages/c6/3a/9c7d864bbcca2df77a601366a6ae3937cd78d0f21ad98441f3424592aea7/cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70", size = 3156882 },
285 | { url = "https://files.pythonhosted.org/packages/17/cd/d43859b09d726a905d882b6e464ccf02aa2dca2c3e76c44a0c5b169f0144/cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66", size = 3722095 },
286 | { url = "https://files.pythonhosted.org/packages/2e/ce/c7b912d95f0ded80ad3b50a0a6b31de813c25d9ffadbe1b26bf22d2c4518/cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f", size = 3928750 },
287 | { url = "https://files.pythonhosted.org/packages/ca/25/7b53082e4c373127c1fb190f70c5aca7bf7a03ac11f67ba15473bc6d9a0e/cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f", size = 3002487 },
288 | { url = "https://files.pythonhosted.org/packages/ba/2a/1bf25f4fa1fd1d315e7ab429539850526b4fbaba0d2eba7813bec242ce6a/cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2", size = 3161239 },
289 | { url = "https://files.pythonhosted.org/packages/0e/aa/fba13d5fcfeaa11dc57ff7b7357b4cc05529a94b29753097e31dde8bcb0d/cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947", size = 3726861 },
290 | { url = "https://files.pythonhosted.org/packages/62/9e/d8c84c24f5c42c7595e975101969009efc440259b59a0a9732cfd4fc4108/cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069", size = 3930709 },
291 | { url = "https://files.pythonhosted.org/packages/ae/35/64282489b9a1a49b79a5543124f24a34242d6daed30f0df7995564c01cf5/cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1", size = 3003354 },
292 | ]
293 |
294 | [[package]]
295 | name = "docutils"
296 | version = "0.20.1"
297 | source = { registry = "https://pypi.org/simple" }
298 | sdist = { url = "https://files.pythonhosted.org/packages/1f/53/a5da4f2c5739cf66290fac1431ee52aff6851c7c8ffd8264f13affd7bcdd/docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b", size = 2058365 }
299 | wheels = [
300 | { url = "https://files.pythonhosted.org/packages/26/87/f238c0670b94533ac0353a4e2a1a771a0cc73277b88bff23d3ae35a256c1/docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", size = 572666 },
301 | ]
302 |
303 | [[package]]
304 | name = "et-xmlfile"
305 | version = "1.1.0"
306 | source = { registry = "https://pypi.org/simple" }
307 | sdist = { url = "https://files.pythonhosted.org/packages/3d/5d/0413a31d184a20c763ad741cc7852a659bf15094c24840c5bdd1754765cd/et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c", size = 3218 }
308 | wheels = [
309 | { url = "https://files.pythonhosted.org/packages/96/c2/3dd434b0108730014f1b96fd286040dc3bcb70066346f7e01ec2ac95865f/et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada", size = 4688 },
310 | ]
311 |
312 | [[package]]
313 | name = "idna"
314 | version = "3.7"
315 | source = { registry = "https://pypi.org/simple" }
316 | sdist = { url = "https://files.pythonhosted.org/packages/21/ed/f86a79a07470cb07819390452f178b3bef1d375f2ec021ecfc709fc7cf07/idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", size = 189575 }
317 | wheels = [
318 | { url = "https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0", size = 66836 },
319 | ]
320 |
321 | [[package]]
322 | name = "importlib-metadata"
323 | version = "8.2.0"
324 | source = { registry = "https://pypi.org/simple" }
325 | dependencies = [
326 | { name = "zipp" },
327 | ]
328 | sdist = { url = "https://files.pythonhosted.org/packages/f6/a1/db39a513aa99ab3442010a994eef1cb977a436aded53042e69bee6959f74/importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d", size = 53907 }
329 | wheels = [
330 | { url = "https://files.pythonhosted.org/packages/82/47/bb25ec04985d0693da478797c3d8c1092b140f3a53ccb984fbbd38affa5b/importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369", size = 25920 },
331 | ]
332 |
333 | [[package]]
334 | name = "isort"
335 | version = "5.13.2"
336 | source = { registry = "https://pypi.org/simple" }
337 | sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 }
338 | wheels = [
339 | { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 },
340 | ]
341 |
342 | [[package]]
343 | name = "jaraco-classes"
344 | version = "3.4.0"
345 | source = { registry = "https://pypi.org/simple" }
346 | dependencies = [
347 | { name = "more-itertools" },
348 | ]
349 | sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780 }
350 | wheels = [
351 | { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777 },
352 | ]
353 |
354 | [[package]]
355 | name = "jaraco-context"
356 | version = "5.3.0"
357 | source = { registry = "https://pypi.org/simple" }
358 | dependencies = [
359 | { name = "backports-tarfile", marker = "python_full_version < '3.12'" },
360 | ]
361 | sdist = { url = "https://files.pythonhosted.org/packages/c9/60/e83781b07f9a66d1d102a0459e5028f3a7816fdd0894cba90bee2bbbda14/jaraco.context-5.3.0.tar.gz", hash = "sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2", size = 13345 }
362 | wheels = [
363 | { url = "https://files.pythonhosted.org/packages/d2/40/11b7bc1898cf1dcb87ccbe09b39f5088634ac78bb25f3383ff541c2b40aa/jaraco.context-5.3.0-py3-none-any.whl", hash = "sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266", size = 6527 },
364 | ]
365 |
366 | [[package]]
367 | name = "jaraco-functools"
368 | version = "4.0.2"
369 | source = { registry = "https://pypi.org/simple" }
370 | dependencies = [
371 | { name = "more-itertools" },
372 | ]
373 | sdist = { url = "https://files.pythonhosted.org/packages/03/b1/6ca3c2052e584e9908a2c146f00378939b3c51b839304ab8ef4de067f042/jaraco_functools-4.0.2.tar.gz", hash = "sha256:3460c74cd0d32bf82b9576bbb3527c4364d5b27a21f5158a62aed6c4b42e23f5", size = 18319 }
374 | wheels = [
375 | { url = "https://files.pythonhosted.org/packages/b1/54/7623e24ffc63730c3a619101361b08860c6b7c7cfc1aef6edb66d80ed708/jaraco.functools-4.0.2-py3-none-any.whl", hash = "sha256:c9d16a3ed4ccb5a889ad8e0b7a343401ee5b2a71cee6ed192d3f68bc351e94e3", size = 9883 },
376 | ]
377 |
378 | [[package]]
379 | name = "jeepney"
380 | version = "0.8.0"
381 | source = { registry = "https://pypi.org/simple" }
382 | sdist = { url = "https://files.pythonhosted.org/packages/d6/f4/154cf374c2daf2020e05c3c6a03c91348d59b23c5366e968feb198306fdf/jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806", size = 106005 }
383 | wheels = [
384 | { url = "https://files.pythonhosted.org/packages/ae/72/2a1e2290f1ab1e06f71f3d0f1646c9e4634e70e1d37491535e19266e8dc9/jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755", size = 48435 },
385 | ]
386 |
387 | [[package]]
388 | name = "keyring"
389 | version = "25.3.0"
390 | source = { registry = "https://pypi.org/simple" }
391 | dependencies = [
392 | { name = "importlib-metadata", marker = "python_full_version < '3.12'" },
393 | { name = "jaraco-classes" },
394 | { name = "jaraco-context" },
395 | { name = "jaraco-functools" },
396 | { name = "jeepney", marker = "sys_platform == 'linux'" },
397 | { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
398 | { name = "secretstorage", marker = "sys_platform == 'linux'" },
399 | ]
400 | sdist = { url = "https://files.pythonhosted.org/packages/32/30/bfdde7294ba6bb2f519950687471dc6a0996d4f77ab30d75c841fa4994ed/keyring-25.3.0.tar.gz", hash = "sha256:8d85a1ea5d6db8515b59e1c5d1d1678b03cf7fc8b8dcfb1651e8c4a524eb42ef", size = 61495 }
401 | wheels = [
402 | { url = "https://files.pythonhosted.org/packages/63/42/ea8c9726e5ee5ff0731978aaf7cd5fa16674cf549c46279b279d7167c2b4/keyring-25.3.0-py3-none-any.whl", hash = "sha256:8d963da00ccdf06e356acd9bf3b743208878751032d8599c6cc89eb51310ffae", size = 38742 },
403 | ]
404 |
405 | [[package]]
406 | name = "markdown-it-py"
407 | version = "3.0.0"
408 | source = { registry = "https://pypi.org/simple" }
409 | dependencies = [
410 | { name = "mdurl" },
411 | ]
412 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
413 | wheels = [
414 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
415 | ]
416 |
417 | [[package]]
418 | name = "mdurl"
419 | version = "0.1.2"
420 | source = { registry = "https://pypi.org/simple" }
421 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
422 | wheels = [
423 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
424 | ]
425 |
426 | [[package]]
427 | name = "more-itertools"
428 | version = "10.4.0"
429 | source = { registry = "https://pypi.org/simple" }
430 | sdist = { url = "https://files.pythonhosted.org/packages/92/0d/ad6a82320cb8eba710fd0dceb0f678d5a1b58d67d03ae5be14874baa39e0/more-itertools-10.4.0.tar.gz", hash = "sha256:fe0e63c4ab068eac62410ab05cccca2dc71ec44ba8ef29916a0090df061cf923", size = 120755 }
431 | wheels = [
432 | { url = "https://files.pythonhosted.org/packages/d8/0b/6a51175e1395774449fca317fb8861379b7a2d59be411b8cce3d19d6ce78/more_itertools-10.4.0-py3-none-any.whl", hash = "sha256:0f7d9f83a0a8dcfa8a2694a770590d98a67ea943e3d9f5298309a484758c4e27", size = 60935 },
433 | ]
434 |
435 | [[package]]
436 | name = "mypy"
437 | version = "1.11.1"
438 | source = { registry = "https://pypi.org/simple" }
439 | dependencies = [
440 | { name = "mypy-extensions" },
441 | { name = "tomli", marker = "python_full_version < '3.11'" },
442 | { name = "typing-extensions" },
443 | ]
444 | sdist = { url = "https://files.pythonhosted.org/packages/b6/9c/a4b3bda53823439cf395db8ecdda6229a83f9bf201714a68a15190bb2919/mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08", size = 3078369 }
445 | wheels = [
446 | { url = "https://files.pythonhosted.org/packages/33/ba/858cc9631c24a349c1c63814edc16448da7d6b8716b2c83a10aa20f5ee89/mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c", size = 10937885 },
447 | { url = "https://files.pythonhosted.org/packages/2d/88/2ae81f7489da8313d0f2043dd657ba847650b00a0fb8e07f40e716ed8c58/mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411", size = 10111978 },
448 | { url = "https://files.pythonhosted.org/packages/df/4b/d211d6036366f9ea5ee9fb949e80d133b4b8496cdde78c7119f518c49734/mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03", size = 12498441 },
449 | { url = "https://files.pythonhosted.org/packages/94/d2/973278d03ad11e006d71d4c858bfe45cf571ae061f3997911925c70a59f0/mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4", size = 13020595 },
450 | { url = "https://files.pythonhosted.org/packages/0b/c2/7f4285eda528883c5c34cb4b8d88080792967f7f7f24256ad8090d303702/mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58", size = 9568307 },
451 | { url = "https://files.pythonhosted.org/packages/0b/b1/62d8ce619493a5364dda4f410912aa12c27126926e8fb8393edca0664640/mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5", size = 10858723 },
452 | { url = "https://files.pythonhosted.org/packages/fe/aa/2ad15a318bc6a17b7f23e1641a624603949904f6131e09681f40340fb875/mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca", size = 10038078 },
453 | { url = "https://files.pythonhosted.org/packages/4d/7f/77feb389d91603f55b3c4e3e16ccf8752bce007ed73ca921e42c9a5dff12/mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de", size = 12420213 },
454 | { url = "https://files.pythonhosted.org/packages/bc/5b/907b4681f68e7ee2e2e88eed65c514cf6406b8f2f83b243ea79bd4eddb97/mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809", size = 12898278 },
455 | { url = "https://files.pythonhosted.org/packages/5b/b3/2a83be637825d7432b8e6a51e45d02de4f463b6c7ec7164a45009a7cf477/mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72", size = 9564438 },
456 | { url = "https://files.pythonhosted.org/packages/3a/34/69638cee2e87303f19a0c35e80d42757e14d9aba328f272fdcdc0bf3c9b8/mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8", size = 10995789 },
457 | { url = "https://files.pythonhosted.org/packages/c4/3c/3e0611348fc53a4a7c80485959478b4f6eae706baf3b7c03cafa22639216/mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a", size = 10002696 },
458 | { url = "https://files.pythonhosted.org/packages/1c/21/a6b46c91b4c9d1918ee59c305f46850cde7cbea748635a352e7c3c8ed204/mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417", size = 12505772 },
459 | { url = "https://files.pythonhosted.org/packages/c4/55/07904d4c8f408e70308015edcbff067eaa77514475938a9dd81b063de2a8/mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e", size = 12954190 },
460 | { url = "https://files.pythonhosted.org/packages/1e/b7/3a50f318979c8c541428c2f1ee973cda813bcc89614de982dafdd0df2b3e/mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525", size = 9663138 },
461 | { url = "https://files.pythonhosted.org/packages/d9/3d/cca659545bd83c67ab784ac2221f8264b3b74e98b2a5aee4f454276ecaf2/mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe", size = 10936143 },
462 | { url = "https://files.pythonhosted.org/packages/83/ff/ec6f1015813adb183925b6f9696062b7fb60b1d60761cc06cea85dd7bcb0/mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c", size = 10105432 },
463 | { url = "https://files.pythonhosted.org/packages/08/a3/ffc2ee9b8689920a524a8013bf05482f51d191d73a8a3939f3d47d9a485a/mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69", size = 12493869 },
464 | { url = "https://files.pythonhosted.org/packages/7b/6b/830fbf3066dcf79831f360c5b1ae3923cfc248aa6a32ea217f179a210126/mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74", size = 13016443 },
465 | { url = "https://files.pythonhosted.org/packages/0a/ae/8ebca638f6bd9e914501a567a56e3ae64fa4d9c8dfff298618e9409d1156/mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b", size = 9567604 },
466 | { url = "https://files.pythonhosted.org/packages/f8/d4/4960d0df55f30a7625d9c3c9414dfd42f779caabae137ef73ffaed0c97b9/mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54", size = 2619257 },
467 | ]
468 |
469 | [[package]]
470 | name = "mypy-extensions"
471 | version = "1.0.0"
472 | source = { registry = "https://pypi.org/simple" }
473 | sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
474 | wheels = [
475 | { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
476 | ]
477 |
478 | [[package]]
479 | name = "nh3"
480 | version = "0.2.18"
481 | source = { registry = "https://pypi.org/simple" }
482 | sdist = { url = "https://files.pythonhosted.org/packages/62/73/10df50b42ddb547a907deeb2f3c9823022580a7a47281e8eae8e003a9639/nh3-0.2.18.tar.gz", hash = "sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4", size = 15028 }
483 | wheels = [
484 | { url = "https://files.pythonhosted.org/packages/b3/89/1daff5d9ba5a95a157c092c7c5f39b8dd2b1ddb4559966f808d31cfb67e0/nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86", size = 1374474 },
485 | { url = "https://files.pythonhosted.org/packages/2c/b6/42fc3c69cabf86b6b81e4c051a9b6e249c5ba9f8155590222c2622961f58/nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811", size = 694573 },
486 | { url = "https://files.pythonhosted.org/packages/45/b9/833f385403abaf0023c6547389ec7a7acf141ddd9d1f21573723a6eab39a/nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200", size = 844082 },
487 | { url = "https://files.pythonhosted.org/packages/05/2b/85977d9e11713b5747595ee61f381bc820749daf83f07b90b6c9964cf932/nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164", size = 782460 },
488 | { url = "https://files.pythonhosted.org/packages/72/f2/5c894d5265ab80a97c68ca36f25c8f6f0308abac649aaf152b74e7e854a8/nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189", size = 879827 },
489 | { url = "https://files.pythonhosted.org/packages/ab/a7/375afcc710dbe2d64cfbd69e31f82f3e423d43737258af01f6a56d844085/nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad", size = 841080 },
490 | { url = "https://files.pythonhosted.org/packages/c2/a8/3bb02d0c60a03ad3a112b76c46971e9480efa98a8946677b5a59f60130ca/nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b", size = 924144 },
491 | { url = "https://files.pythonhosted.org/packages/1b/63/6ab90d0e5225ab9780f6c9fb52254fa36b52bb7c188df9201d05b647e5e1/nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307", size = 769192 },
492 | { url = "https://files.pythonhosted.org/packages/a4/17/59391c28580e2c32272761629893e761442fc7666da0b1cdb479f3b67b88/nh3-0.2.18-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f", size = 791042 },
493 | { url = "https://files.pythonhosted.org/packages/a3/da/0c4e282bc3cff4a0adf37005fa1fb42257673fbc1bbf7d1ff639ec3d255a/nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe", size = 1010073 },
494 | { url = "https://files.pythonhosted.org/packages/de/81/c291231463d21da5f8bba82c8167a6d6893cc5419b0639801ee5d3aeb8a9/nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a", size = 1029782 },
495 | { url = "https://files.pythonhosted.org/packages/63/1d/842fed85cf66c973be0aed8770093d6a04741f65e2c388ddd4c07fd3296e/nh3-0.2.18-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50", size = 942504 },
496 | { url = "https://files.pythonhosted.org/packages/eb/61/73a007c74c37895fdf66e0edcd881f5eaa17a348ff02f4bb4bc906d61085/nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204", size = 941541 },
497 | { url = "https://files.pythonhosted.org/packages/78/48/54a788fc9428e481b2f58e0cd8564f6c74ffb6e9ef73d39e8acbeae8c629/nh3-0.2.18-cp37-abi3-win32.whl", hash = "sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be", size = 573750 },
498 | { url = "https://files.pythonhosted.org/packages/26/8d/53c5b19c4999bdc6ba95f246f4ef35ca83d7d7423e5e38be43ad66544e5d/nh3-0.2.18-cp37-abi3-win_amd64.whl", hash = "sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844", size = 579012 },
499 | ]
500 |
501 | [[package]]
502 | name = "numpy"
503 | version = "2.0.1"
504 | source = { registry = "https://pypi.org/simple" }
505 | sdist = { url = "https://files.pythonhosted.org/packages/1c/8a/0db635b225d2aa2984e405dc14bd2b0c324a0c312ea1bc9d283f2b83b038/numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3", size = 18872007 }
506 | wheels = [
507 | { url = "https://files.pythonhosted.org/packages/86/c0/025580db782c9be4c7de992e2cc4b2930c12ef8e0f26389c88089e2f8028/numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134", size = 21234466 },
508 | { url = "https://files.pythonhosted.org/packages/d6/47/58ad880b4a6ae4ec01e212d68d298cc5425814496e8fce7b35575880984c/numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42", size = 13369204 },
509 | { url = "https://files.pythonhosted.org/packages/5f/f7/d9db3db9ff23af6ed1d948441a4805cfcb24119a3edbe1d5d8bcc89b2496/numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02", size = 5300247 },
510 | { url = "https://files.pythonhosted.org/packages/b5/ec/d8faaa60f6e39227b3c7286db9db1de48b6f51227d7e64223d22d2374e3e/numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101", size = 6899834 },
511 | { url = "https://files.pythonhosted.org/packages/08/7a/71b12fe40147b1a6146ffd07afbe57d3c0ea98560032fae7c6d81c861b09/numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9", size = 13907521 },
512 | { url = "https://files.pythonhosted.org/packages/ac/9c/703d6775b99ae37c3d4fc32984953572fb20e23e61c0f4154f05e5758a30/numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015", size = 19530562 },
513 | { url = "https://files.pythonhosted.org/packages/be/53/4c639cc2e0331ec42b54368648f23330f9ff406c74f2f0ccca6702ba6b71/numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87", size = 19929256 },
514 | { url = "https://files.pythonhosted.org/packages/91/ec/18a1b896e25f0bba8aa3c3b010e7aaf62387583db368b29a571519c2dd45/numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82", size = 14413740 },
515 | { url = "https://files.pythonhosted.org/packages/3c/f6/00efe4505061b100a907c55d60765b020be3b08accff37d1444d02ae108a/numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1", size = 6469150 },
516 | { url = "https://files.pythonhosted.org/packages/16/9f/1fcb7fdb6ec988052b42c7f4ed6d92d89c541d640f8f39f01bf141d17426/numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868", size = 16554905 },
517 | { url = "https://files.pythonhosted.org/packages/29/d6/ff66f4f87518a435538e15cc9e0477a88398512a18783e748914f0daf5ea/numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268", size = 21238269 },
518 | { url = "https://files.pythonhosted.org/packages/c5/64/853cfc37494471e64ea9f7bf3bc3b4bb39450e6db5beeb05e2a66beef612/numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e", size = 13334324 },
519 | { url = "https://files.pythonhosted.org/packages/d1/d8/597b4b2e396a77cbec677c9de33bb1789d5c3b66d653cb723d00eb331e99/numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343", size = 5298376 },
520 | { url = "https://files.pythonhosted.org/packages/64/58/8664ff3747ac719ae1a5b9c0020533435158180a27f2f88a2b7a253bb623/numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b", size = 6903817 },
521 | { url = "https://files.pythonhosted.org/packages/72/44/71ac0090d4ccb512fcac0ef0e5208248423a1ce30381541700470ac09b75/numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe", size = 13916254 },
522 | { url = "https://files.pythonhosted.org/packages/ef/27/39622993e8688a1f05898a3c3b2836b856f79c06637ebd4b71cb35cc9b18/numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67", size = 19540260 },
523 | { url = "https://files.pythonhosted.org/packages/6a/26/a32b5a6b3f090860aeefb3619bfea09f717d73908bd65e69e8ab0cac9c07/numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7", size = 19938602 },
524 | { url = "https://files.pythonhosted.org/packages/34/b6/a88a9953d0be231c67aa0b3714d6138507490753beaa927f0b33f20cdca2/numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55", size = 14425300 },
525 | { url = "https://files.pythonhosted.org/packages/e4/e2/e763e102bea9c188b43ea144a91c22bec669736889a6e0be0235d64666d7/numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4", size = 6467652 },
526 | { url = "https://files.pythonhosted.org/packages/3d/67/928e8f0d5c7fd32f32fb5caf92b186a1b3826dbaf5a294e13a976d6c38b6/numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8", size = 16559280 },
527 | { url = "https://files.pythonhosted.org/packages/64/1c/401489a7e92c30db413362756c313b9353fb47565015986c55582593e2ae/numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac", size = 20965374 },
528 | { url = "https://files.pythonhosted.org/packages/08/61/460fb524bb2d1a8bd4bbcb33d9b0971f9837fdedcfda8478d4c8f5cfd7ee/numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4", size = 13102536 },
529 | { url = "https://files.pythonhosted.org/packages/c2/da/3d8debb409bc97045b559f408d2b8cefa6a077a73df14dbf4d8780d976b1/numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1", size = 5037809 },
530 | { url = "https://files.pythonhosted.org/packages/6d/59/851609f533e7bf5f4af6264a7c5149ab07be9c8db2b0eb064794f8a7bf6d/numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587", size = 6631813 },
531 | { url = "https://files.pythonhosted.org/packages/5e/e3/944b70438d3b7e2742fece7da8dfba6f7ef7dccdd163d1a613f7027f4d5b/numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8", size = 13623742 },
532 | { url = "https://files.pythonhosted.org/packages/2c/f3/61eeef119beb37decb58e7cb29940f19a1464b8608f2cab8a8616aba75fd/numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a", size = 19242336 },
533 | { url = "https://files.pythonhosted.org/packages/77/b5/c74cc1c91754436114c1de5912cdb475145245f6e645a6a1a29b5d08c774/numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7", size = 19637264 },
534 | { url = "https://files.pythonhosted.org/packages/da/89/c8856d3fd5fce12e0b3f6af371ccb90d604600923b08050c58f0cd26eac9/numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b", size = 14108911 },
535 | { url = "https://files.pythonhosted.org/packages/15/96/310c6f3f2447f6d146518479b0a6ee6eb92a537954ec3b1acfa2894d1347/numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf", size = 6171379 },
536 | { url = "https://files.pythonhosted.org/packages/b5/59/f6ad30785a6578ad85ed9c2785f271b39c3e5b6412c66e810d2c60934c9f/numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171", size = 16255757 },
537 | { url = "https://files.pythonhosted.org/packages/e9/8b/405e7dba00aa96ff03f5fef3b6fb3b6f113b13311f70faa72185696fbe0a/numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414", size = 21239254 },
538 | { url = "https://files.pythonhosted.org/packages/a5/e4/280150a359c0c3039d7965f6ade0c9324961b318f5e07cb36dc28915c0a6/numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef", size = 13342134 },
539 | { url = "https://files.pythonhosted.org/packages/cc/4e/3f7e4fed86ec3c940ba84fe819eb5f2288de85a5f29679a9ed5fbb55dc43/numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06", size = 5306698 },
540 | { url = "https://files.pythonhosted.org/packages/97/de/9bf6c37a8f760847172b13691ac83c404c8485a090b349244e2758c8436d/numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c", size = 6904663 },
541 | { url = "https://files.pythonhosted.org/packages/bd/54/15a0ba87e6335d02475201c9767a6a424ee39ed438ebdb6438f34abc2c25/numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59", size = 13913787 },
542 | { url = "https://files.pythonhosted.org/packages/b1/e3/24d289c5a3255bf52824bd52295e9a7923cad8ae5ec29539fc971e1122f6/numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26", size = 19533058 },
543 | { url = "https://files.pythonhosted.org/packages/0a/ea/a0c96ffd46214e5fdb4e12cfa34824468503295d6e2fdeef2e2b1de30cd7/numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018", size = 19934441 },
544 | { url = "https://files.pythonhosted.org/packages/c3/c4/763efe6af1925baa6c433ee926fef6996eedc7c2cb2485593de01a24e8f5/numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c", size = 14417003 },
545 | { url = "https://files.pythonhosted.org/packages/35/a0/2efec9d64c64db60200909800ca04ade088bc9942641e121cf9933a53d11/numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4", size = 6473865 },
546 | { url = "https://files.pythonhosted.org/packages/52/87/bb45780eb4b9ed1e4710c2f2b42ed7224071aef6f08152f2520df0ec2ee5/numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368", size = 16560729 },
547 | { url = "https://files.pythonhosted.org/packages/2a/a3/0a6e85731c9a5ac646cf873d02dca843c6c00fc98ed979bc59ade283ad31/numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f", size = 21094924 },
548 | { url = "https://files.pythonhosted.org/packages/1a/e8/ba6f8f5ee68c7459820236a1e2a76ab4eccad6b6b7f090dabffb7c62ca55/numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d", size = 6760873 },
549 | { url = "https://files.pythonhosted.org/packages/3b/f1/d156a9f3adbdbfdf97e8d0f25e32ee943200ed743c8af7a207d92ebcf61d/numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990", size = 19337879 },
550 | { url = "https://files.pythonhosted.org/packages/41/30/260f1848653080d7ed1780e85547cdca50d858c6d38e835b2c8d7ac7cad9/numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f", size = 16477335 },
551 | ]
552 |
553 | [[package]]
554 | name = "openai"
555 | version = "0.23.1"
556 | source = { registry = "https://pypi.org/simple" }
557 | dependencies = [
558 | { name = "numpy" },
559 | { name = "openpyxl" },
560 | { name = "pandas" },
561 | { name = "pandas-stubs" },
562 | { name = "requests" },
563 | { name = "tqdm" },
564 | { name = "typing-extensions" },
565 | ]
566 | sdist = { url = "https://files.pythonhosted.org/packages/be/92/91da375955142026e05dd0936f425c324924d55ac726d71bcfff5eb368c6/openai-0.23.1.tar.gz", hash = "sha256:16703a2433c1fc71b913057062d00b86dd2ad714dfd7b2b1ab803ac9b31fba5f", size = 43793 }
567 |
568 | [[package]]
569 | name = "openpyxl"
570 | version = "3.1.5"
571 | source = { registry = "https://pypi.org/simple" }
572 | dependencies = [
573 | { name = "et-xmlfile" },
574 | ]
575 | sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 }
576 | wheels = [
577 | { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 },
578 | ]
579 |
580 | [[package]]
581 | name = "packaging"
582 | version = "24.1"
583 | source = { registry = "https://pypi.org/simple" }
584 | sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 }
585 | wheels = [
586 | { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 },
587 | ]
588 |
589 | [[package]]
590 | name = "pandas"
591 | version = "2.2.2"
592 | source = { registry = "https://pypi.org/simple" }
593 | dependencies = [
594 | { name = "numpy" },
595 | { name = "python-dateutil" },
596 | { name = "pytz" },
597 | { name = "tzdata" },
598 | ]
599 | sdist = { url = "https://files.pythonhosted.org/packages/88/d9/ecf715f34c73ccb1d8ceb82fc01cd1028a65a5f6dbc57bfa6ea155119058/pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54", size = 4398391 }
600 | wheels = [
601 | { url = "https://files.pythonhosted.org/packages/d1/2d/39600d073ea70b9cafdc51fab91d69c72b49dd92810f24cb5ac6631f387f/pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce", size = 12551798 },
602 | { url = "https://files.pythonhosted.org/packages/fd/4b/0cd38e68ab690b9df8ef90cba625bf3f93b82d1c719703b8e1b333b2c72d/pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238", size = 11287392 },
603 | { url = "https://files.pythonhosted.org/packages/01/c6/d3d2612aea9b9f28e79a30b864835dad8f542dcf474eee09afeee5d15d75/pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08", size = 15634823 },
604 | { url = "https://files.pythonhosted.org/packages/89/1b/12521efcbc6058e2673583bb096c2b5046a9df39bd73eca392c1efed24e5/pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0", size = 13032214 },
605 | { url = "https://files.pythonhosted.org/packages/e4/d7/303dba73f1c3a9ef067d23e5afbb6175aa25e8121be79be354dcc740921a/pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51", size = 16278302 },
606 | { url = "https://files.pythonhosted.org/packages/ba/df/8ff7c5ed1cc4da8c6ab674dc8e4860a4310c3880df1283e01bac27a4333d/pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99", size = 13892866 },
607 | { url = "https://files.pythonhosted.org/packages/69/a6/81d5dc9a612cf0c1810c2ebc4f2afddb900382276522b18d128213faeae3/pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772", size = 11621592 },
608 | { url = "https://files.pythonhosted.org/packages/1b/70/61704497903d43043e288017cb2b82155c0d41e15f5c17807920877b45c2/pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288", size = 12574808 },
609 | { url = "https://files.pythonhosted.org/packages/16/c6/75231fd47afd6b3f89011e7077f1a3958441264aca7ae9ff596e3276a5d0/pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151", size = 11304876 },
610 | { url = "https://files.pythonhosted.org/packages/97/2d/7b54f80b93379ff94afb3bd9b0cd1d17b48183a0d6f98045bc01ce1e06a7/pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b", size = 15602548 },
611 | { url = "https://files.pythonhosted.org/packages/fc/a5/4d82be566f069d7a9a702dcdf6f9106df0e0b042e738043c0cc7ddd7e3f6/pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee", size = 13031332 },
612 | { url = "https://files.pythonhosted.org/packages/92/a2/b79c48f530673567805e607712b29814b47dcaf0d167e87145eb4b0118c6/pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db", size = 16286054 },
613 | { url = "https://files.pythonhosted.org/packages/40/c7/47e94907f1d8fdb4868d61bd6c93d57b3784a964d52691b77ebfdb062842/pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1", size = 13879507 },
614 | { url = "https://files.pythonhosted.org/packages/ab/63/966db1321a0ad55df1d1fe51505d2cdae191b84c907974873817b0a6e849/pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24", size = 11634249 },
615 | { url = "https://files.pythonhosted.org/packages/dd/49/de869130028fb8d90e25da3b7d8fb13e40f5afa4c4af1781583eb1ff3839/pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef", size = 12500886 },
616 | { url = "https://files.pythonhosted.org/packages/db/7c/9a60add21b96140e22465d9adf09832feade45235cd22f4cb1668a25e443/pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce", size = 11340320 },
617 | { url = "https://files.pythonhosted.org/packages/b0/85/f95b5f322e1ae13b7ed7e97bd999160fa003424711ab4dc8344b8772c270/pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad", size = 15204346 },
618 | { url = "https://files.pythonhosted.org/packages/40/10/79e52ef01dfeb1c1ca47a109a01a248754ebe990e159a844ece12914de83/pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad", size = 12733396 },
619 | { url = "https://files.pythonhosted.org/packages/35/9d/208febf8c4eb5c1d9ea3314d52d8bd415fd0ef0dd66bb24cc5bdbc8fa71a/pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76", size = 15858913 },
620 | { url = "https://files.pythonhosted.org/packages/99/d1/2d9bd05def7a9e08a92ec929b5a4c8d5556ec76fae22b0fa486cbf33ea63/pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32", size = 13417786 },
621 | { url = "https://files.pythonhosted.org/packages/22/a5/a0b255295406ed54269814bc93723cfd1a0da63fb9aaf99e1364f07923e5/pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23", size = 11498828 },
622 | { url = "https://files.pythonhosted.org/packages/1b/cc/eb6ce83667131667c6561e009823e72aa5c76698e75552724bdfc8d1ef0b/pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2", size = 12566406 },
623 | { url = "https://files.pythonhosted.org/packages/96/08/9ad65176f854fd5eb806a27da6e8b6c12d5ddae7ef3bd80d8b3009099333/pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd", size = 11304008 },
624 | { url = "https://files.pythonhosted.org/packages/aa/30/5987c82fea318ac7d6bcd083c5b5259d4000e99dd29ae7a9357c65a1b17a/pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863", size = 15662279 },
625 | { url = "https://files.pythonhosted.org/packages/bb/30/f6f1f1ac36250f50c421b1b6af08c35e5a8b5a84385ef928625336b93e6f/pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921", size = 13069490 },
626 | { url = "https://files.pythonhosted.org/packages/b5/27/76c1509f505d1f4cb65839352d099c90a13019371e90347166811aa6a075/pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a", size = 16299412 },
627 | { url = "https://files.pythonhosted.org/packages/5d/11/a5a2f52936fba3afc42de35b19cae941284d973649cb6949bc41cc2e5901/pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57", size = 13920884 },
628 | { url = "https://files.pythonhosted.org/packages/bf/2c/a0cee9c392a4c9227b835af27f9260582b994f9a2b5ec23993b596e5deb7/pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4", size = 11637580 },
629 | ]
630 |
631 | [[package]]
632 | name = "pandas-stubs"
633 | version = "2.2.2.240807"
634 | source = { registry = "https://pypi.org/simple" }
635 | dependencies = [
636 | { name = "numpy" },
637 | { name = "types-pytz" },
638 | ]
639 | sdist = { url = "https://files.pythonhosted.org/packages/1f/df/0da95bc75c76f1e012e0bc0b76da31faaf4254e94b9870f25e6311145e98/pandas_stubs-2.2.2.240807.tar.gz", hash = "sha256:64a559725a57a449f46225fbafc422520b7410bff9252b661a225b5559192a93", size = 103095 }
640 | wheels = [
641 | { url = "https://files.pythonhosted.org/packages/0a/f9/22c91632ea1b4c6165952f677bf9ad95f9ac36ffd7ef3e6450144e6d8b1a/pandas_stubs-2.2.2.240807-py3-none-any.whl", hash = "sha256:893919ad82be4275f0d07bb47a95d08bae580d3fdea308a7acfcb3f02e76186e", size = 157069 },
642 | ]
643 |
644 | [[package]]
645 | name = "pathspec"
646 | version = "0.12.1"
647 | source = { registry = "https://pypi.org/simple" }
648 | sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
649 | wheels = [
650 | { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
651 | ]
652 |
653 | [[package]]
654 | name = "pkginfo"
655 | version = "1.10.0"
656 | source = { registry = "https://pypi.org/simple" }
657 | sdist = { url = "https://files.pythonhosted.org/packages/2f/72/347ec5be4adc85c182ed2823d8d1c7b51e13b9a6b0c1aae59582eca652df/pkginfo-1.10.0.tar.gz", hash = "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297", size = 378457 }
658 | wheels = [
659 | { url = "https://files.pythonhosted.org/packages/56/09/054aea9b7534a15ad38a363a2bd974c20646ab1582a387a95b8df1bfea1c/pkginfo-1.10.0-py3-none-any.whl", hash = "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097", size = 30392 },
660 | ]
661 |
662 | [[package]]
663 | name = "platformdirs"
664 | version = "4.2.2"
665 | source = { registry = "https://pypi.org/simple" }
666 | sdist = { url = "https://files.pythonhosted.org/packages/f5/52/0763d1d976d5c262df53ddda8d8d4719eedf9594d046f117c25a27261a19/platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3", size = 20916 }
667 | wheels = [
668 | { url = "https://files.pythonhosted.org/packages/68/13/2aa1f0e1364feb2c9ef45302f387ac0bd81484e9c9a4c5688a322fbdfd08/platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", size = 18146 },
669 | ]
670 |
671 | [[package]]
672 | name = "pycparser"
673 | version = "2.22"
674 | source = { registry = "https://pypi.org/simple" }
675 | sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 }
676 | wheels = [
677 | { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 },
678 | ]
679 |
680 | [[package]]
681 | name = "pygments"
682 | version = "2.18.0"
683 | source = { registry = "https://pypi.org/simple" }
684 | sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 }
685 | wheels = [
686 | { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
687 | ]
688 |
689 | [[package]]
690 | name = "python-dateutil"
691 | version = "2.9.0.post0"
692 | source = { registry = "https://pypi.org/simple" }
693 | dependencies = [
694 | { name = "six" },
695 | ]
696 | sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
697 | wheels = [
698 | { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
699 | ]
700 |
701 | [[package]]
702 | name = "python-dotenv"
703 | version = "1.0.1"
704 | source = { registry = "https://pypi.org/simple" }
705 | sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
706 | wheels = [
707 | { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
708 | ]
709 |
710 | [[package]]
711 | name = "pytz"
712 | version = "2024.1"
713 | source = { registry = "https://pypi.org/simple" }
714 | sdist = { url = "https://files.pythonhosted.org/packages/90/26/9f1f00a5d021fff16dee3de13d43e5e978f3d58928e129c3a62cf7eb9738/pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", size = 316214 }
715 | wheels = [
716 | { url = "https://files.pythonhosted.org/packages/9c/3d/a121f284241f08268b21359bd425f7d4825cffc5ac5cd0e1b3d82ffd2b10/pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319", size = 505474 },
717 | ]
718 |
719 | [[package]]
720 | name = "pywin32-ctypes"
721 | version = "0.2.2"
722 | source = { registry = "https://pypi.org/simple" }
723 | sdist = { url = "https://files.pythonhosted.org/packages/10/3d/0cfbca45201351fe8c09cca743403e6c2407892e256e25d126ad64dc6bb7/pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60", size = 26950 }
724 | wheels = [
725 | { url = "https://files.pythonhosted.org/packages/a4/bc/78b2c00cc64c31dbb3be42a0e8600bcebc123ad338c3b714754d668c7c2d/pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7", size = 30152 },
726 | ]
727 |
728 | [[package]]
729 | name = "readme-renderer"
730 | version = "43.0"
731 | source = { registry = "https://pypi.org/simple" }
732 | dependencies = [
733 | { name = "docutils" },
734 | { name = "nh3" },
735 | { name = "pygments" },
736 | ]
737 | sdist = { url = "https://files.pythonhosted.org/packages/fe/b5/536c775084d239df6345dccf9b043419c7e3308bc31be4c7882196abc62e/readme_renderer-43.0.tar.gz", hash = "sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311", size = 31768 }
738 | wheels = [
739 | { url = "https://files.pythonhosted.org/packages/45/be/3ea20dc38b9db08387cf97997a85a7d51527ea2057d71118feb0aa8afa55/readme_renderer-43.0-py3-none-any.whl", hash = "sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9", size = 13301 },
740 | ]
741 |
742 | [[package]]
743 | name = "requests"
744 | version = "2.32.3"
745 | source = { registry = "https://pypi.org/simple" }
746 | dependencies = [
747 | { name = "certifi" },
748 | { name = "charset-normalizer" },
749 | { name = "idna" },
750 | { name = "urllib3" },
751 | ]
752 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
753 | wheels = [
754 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
755 | ]
756 |
757 | [[package]]
758 | name = "requests-toolbelt"
759 | version = "1.0.0"
760 | source = { registry = "https://pypi.org/simple" }
761 | dependencies = [
762 | { name = "requests" },
763 | ]
764 | sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 }
765 | wheels = [
766 | { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 },
767 | ]
768 |
769 | [[package]]
770 | name = "rfc3986"
771 | version = "2.0.0"
772 | source = { registry = "https://pypi.org/simple" }
773 | sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026 }
774 | wheels = [
775 | { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326 },
776 | ]
777 |
778 | [[package]]
779 | name = "rich"
780 | version = "13.7.1"
781 | source = { registry = "https://pypi.org/simple" }
782 | dependencies = [
783 | { name = "markdown-it-py" },
784 | { name = "pygments" },
785 | ]
786 | sdist = { url = "https://files.pythonhosted.org/packages/b3/01/c954e134dc440ab5f96952fe52b4fdc64225530320a910473c1fe270d9aa/rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432", size = 221248 }
787 | wheels = [
788 | { url = "https://files.pythonhosted.org/packages/87/67/a37f6214d0e9fe57f6ae54b2956d550ca8365857f42a1ce0392bb21d9410/rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", size = 240681 },
789 | ]
790 |
791 | [[package]]
792 | name = "ruff"
793 | version = "0.5.6"
794 | source = { registry = "https://pypi.org/simple" }
795 | sdist = { url = "https://files.pythonhosted.org/packages/f7/69/96766da2cdb5605e6a31ef2734aff0be17901cefb385b885c2ab88896d76/ruff-0.5.6.tar.gz", hash = "sha256:07c9e3c2a8e1fe377dd460371c3462671a728c981c3205a5217291422209f642", size = 2444466 }
796 | wheels = [
797 | { url = "https://files.pythonhosted.org/packages/85/54/af603686de0e9ba6c1798bd83d335e345e7c9ce4deed95a1071448bb6563/ruff-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:a0ef5930799a05522985b9cec8290b185952f3fcd86c1772c3bdbd732667fdcd", size = 9543715 },
798 | { url = "https://files.pythonhosted.org/packages/81/86/b22d250e1be2dcd11c749817d0cfe8be37efce6e6862c92ea037bedf6df9/ruff-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b652dc14f6ef5d1552821e006f747802cc32d98d5509349e168f6bf0ee9f8f42", size = 8662814 },
799 | { url = "https://files.pythonhosted.org/packages/d2/e2/f97c58797fac1c7dd8226becfed55056fa7bbfb6d9c2e93900f2a27b540e/ruff-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:80521b88d26a45e871f31e4b88938fd87db7011bb961d8afd2664982dfc3641a", size = 8230427 },
800 | { url = "https://files.pythonhosted.org/packages/24/70/9f00cf1e2b50476751808ac6ebbfaa6dab3abab5514058f6bbca28a790bc/ruff-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9bc8f328a9f1309ae80e4d392836e7dbc77303b38ed4a7112699e63d3b066ab", size = 9976713 },
801 | { url = "https://files.pythonhosted.org/packages/9b/02/309b171d867d819b7c76c46489597341ce85d70fefa05102a95130440005/ruff-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d394940f61f7720ad371ddedf14722ee1d6250fd8d020f5ea5a86e7be217daf", size = 9345335 },
802 | { url = "https://files.pythonhosted.org/packages/37/42/74b84b97815a0b940f1438ff554ead1405d2556ff8e2da58d3c3068a2093/ruff-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111a99cdb02f69ddb2571e2756e017a1496c2c3a2aeefe7b988ddab38b416d36", size = 10140380 },
803 | { url = "https://files.pythonhosted.org/packages/47/0c/705b979fe814498dfc3977bcdbf8c47059d2ed37b1c2128f1b77612eea82/ruff-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e395daba77a79f6dc0d07311f94cc0560375ca20c06f354c7c99af3bf4560c5d", size = 10853490 },
804 | { url = "https://files.pythonhosted.org/packages/56/f1/400cc9a533fadba676783a2116526d1c26063d3228105bc75fa1c32a092b/ruff-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c476acb43c3c51e3c614a2e878ee1589655fa02dab19fe2db0423a06d6a5b1b6", size = 10425500 },
805 | { url = "https://files.pythonhosted.org/packages/f3/cd/f2cf6bc23a4293e3f8c365138f7279eea9179d1c4d3e09546d64daea40da/ruff-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2ff8003f5252fd68425fd53d27c1f08b201d7ed714bb31a55c9ac1d4c13e2eb", size = 11407463 },
806 | { url = "https://files.pythonhosted.org/packages/f8/cc/227428f39f8834d281b6d18f84f53e08a840ed63dec259e5e5e502284cec/ruff-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c94e084ba3eaa80c2172918c2ca2eb2230c3f15925f4ed8b6297260c6ef179ad", size = 10160402 },
807 | { url = "https://files.pythonhosted.org/packages/f3/61/218890d960ca146d3822e981a34834995081f3b741b82ca2585fd5ebc521/ruff-0.5.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f77c1c3aa0669fb230b06fb24ffa3e879391a3ba3f15e3d633a752da5a3e670", size = 9971344 },
808 | { url = "https://files.pythonhosted.org/packages/f1/20/bd444745ff7b51e497332ecf3f550bb62871dd24d61960997049fdbed268/ruff-0.5.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f908148c93c02873210a52cad75a6eda856b2cbb72250370ce3afef6fb99b1ed", size = 9417963 },
809 | { url = "https://files.pythonhosted.org/packages/30/03/69b6d3735f284d93324ca31e36316574501130393f1a8e328a74775f4fb5/ruff-0.5.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:563a7ae61ad284187d3071d9041c08019975693ff655438d8d4be26e492760bd", size = 9769870 },
810 | { url = "https://files.pythonhosted.org/packages/7f/19/1c86dc40abef457124bb4abf90c68688eb2c7f9944903240c3b93d511360/ruff-0.5.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:94fe60869bfbf0521e04fd62b74cbca21cbc5beb67cbb75ab33fe8c174f54414", size = 10229991 },
811 | { url = "https://files.pythonhosted.org/packages/65/a7/60a19f3cfccdea5815bc31971f523f5b2ecd08162b05d1a0d5b8c37dff59/ruff-0.5.6-py3-none-win32.whl", hash = "sha256:e6a584c1de6f8591c2570e171cc7ce482bb983d49c70ddf014393cd39e9dfaed", size = 7789030 },
812 | { url = "https://files.pythonhosted.org/packages/27/c6/b51987072bba4a56537b6fc7036d9bf0694a9de6cceddaa0761475658632/ruff-0.5.6-py3-none-win_amd64.whl", hash = "sha256:d7fe7dccb1a89dc66785d7aa0ac283b2269712d8ed19c63af908fdccca5ccc1a", size = 8690520 },
813 | { url = "https://files.pythonhosted.org/packages/c0/b1/712801cf487fd78554029b02f980e24531f5994e2bf79b7ac55c1659dd52/ruff-0.5.6-py3-none-win_arm64.whl", hash = "sha256:57c6c0dd997b31b536bff49b9eee5ed3194d60605a4427f735eeb1f9c1b8d264", size = 8101938 },
814 | ]
815 |
816 | [[package]]
817 | name = "secretstorage"
818 | version = "3.3.3"
819 | source = { registry = "https://pypi.org/simple" }
820 | dependencies = [
821 | { name = "cryptography" },
822 | { name = "jeepney" },
823 | ]
824 | sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739 }
825 | wheels = [
826 | { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221 },
827 | ]
828 |
829 | [[package]]
830 | name = "six"
831 | version = "1.16.0"
832 | source = { registry = "https://pypi.org/simple" }
833 | sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 }
834 | wheels = [
835 | { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
836 | ]
837 |
838 | [[package]]
839 | name = "tomli"
840 | version = "2.0.1"
841 | source = { registry = "https://pypi.org/simple" }
842 | sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 }
843 | wheels = [
844 | { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 },
845 | ]
846 |
847 | [[package]]
848 | name = "tqdm"
849 | version = "4.66.5"
850 | source = { registry = "https://pypi.org/simple" }
851 | dependencies = [
852 | { name = "colorama", marker = "platform_system == 'Windows'" },
853 | ]
854 | sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 }
855 | wheels = [
856 | { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351 },
857 | ]
858 |
859 | [[package]]
860 | name = "twine"
861 | version = "5.1.1"
862 | source = { registry = "https://pypi.org/simple" }
863 | dependencies = [
864 | { name = "importlib-metadata" },
865 | { name = "keyring" },
866 | { name = "pkginfo" },
867 | { name = "readme-renderer" },
868 | { name = "requests" },
869 | { name = "requests-toolbelt" },
870 | { name = "rfc3986" },
871 | { name = "rich" },
872 | { name = "urllib3" },
873 | ]
874 | sdist = { url = "https://files.pythonhosted.org/packages/77/68/bd982e5e949ef8334e6f7dcf76ae40922a8750aa2e347291ae1477a4782b/twine-5.1.1.tar.gz", hash = "sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db", size = 225531 }
875 | wheels = [
876 | { url = "https://files.pythonhosted.org/packages/5d/ec/00f9d5fd040ae29867355e559a94e9a8429225a0284a3f5f091a3878bfc0/twine-5.1.1-py3-none-any.whl", hash = "sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997", size = 38650 },
877 | ]
878 |
879 | [[package]]
880 | name = "types-pytz"
881 | version = "2024.1.0.20240417"
882 | source = { registry = "https://pypi.org/simple" }
883 | sdist = { url = "https://files.pythonhosted.org/packages/9b/b0/079f6f340c0051fbe03ac3a6d9fce323c9797b85380d455e1566eaf2716b/types-pytz-2024.1.0.20240417.tar.gz", hash = "sha256:6810c8a1f68f21fdf0f4f374a432487c77645a0ac0b31de4bf4690cf21ad3981", size = 5459 }
884 | wheels = [
885 | { url = "https://files.pythonhosted.org/packages/e8/8d/f5dc5239d59bb4a7b58e2b6d0dc6f2c2ba797b110f83cdda8479508c63dd/types_pytz-2024.1.0.20240417-py3-none-any.whl", hash = "sha256:8335d443310e2db7b74e007414e74c4f53b67452c0cb0d228ca359ccfba59659", size = 5246 },
886 | ]
887 |
888 | [[package]]
889 | name = "typing-extensions"
890 | version = "4.12.2"
891 | source = { registry = "https://pypi.org/simple" }
892 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
893 | wheels = [
894 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
895 | ]
896 |
897 | [[package]]
898 | name = "tzdata"
899 | version = "2024.1"
900 | source = { registry = "https://pypi.org/simple" }
901 | sdist = { url = "https://files.pythonhosted.org/packages/74/5b/e025d02cb3b66b7b76093404392d4b44343c69101cc85f4d180dd5784717/tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", size = 190559 }
902 | wheels = [
903 | { url = "https://files.pythonhosted.org/packages/65/58/f9c9e6be752e9fcb8b6a0ee9fb87e6e7a1f6bcab2cdc73f02bb7ba91ada0/tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252", size = 345370 },
904 | ]
905 |
906 | [[package]]
907 | name = "urllib3"
908 | version = "2.2.2"
909 | source = { registry = "https://pypi.org/simple" }
910 | sdist = { url = "https://files.pythonhosted.org/packages/43/6d/fa469ae21497ddc8bc93e5877702dca7cb8f911e337aca7452b5724f1bb6/urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168", size = 292266 }
911 | wheels = [
912 | { url = "https://files.pythonhosted.org/packages/ca/1c/89ffc63a9605b583d5df2be791a27bc1a42b7c32bab68d3c8f2f73a98cd4/urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", size = 121444 },
913 | ]
914 |
915 | [[package]]
916 | name = "zipp"
917 | version = "3.19.2"
918 | source = { registry = "https://pypi.org/simple" }
919 | sdist = { url = "https://files.pythonhosted.org/packages/d3/20/b48f58857d98dcb78f9e30ed2cfe533025e2e9827bbd36ea0a64cc00cbc1/zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19", size = 22922 }
920 | wheels = [
921 | { url = "https://files.pythonhosted.org/packages/20/38/f5c473fe9b90c8debdd29ea68d5add0289f1936d6f923b6b9cc0b931194c/zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c", size = 9039 },
922 | ]
923 |
--------------------------------------------------------------------------------