From a79b5a8d87c456e8168808af0c263233e243cef0 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 18 Oct 2025 02:17:47 -0400 Subject: [PATCH 001/108] minor: update CLI command with params field and update Makefile to allow proper uv.lock file update --- Makefile | 4 +-- pyproject.toml | 4 +++ src/fpds/cli/__init__.py | 18 +++++++++++-- src/fpds/cli/params.py | 55 ++++++++++++++++++++++++++++++++++++++++ uv.lock | 19 ++++++++++++-- 5 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 src/fpds/cli/params.py diff --git a/Makefile b/Makefile index 86c74049..f3921a91 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,8 @@ venv: ## defaults to creating virtual environment in current directory under .ve uv venv; \ fi -install: venv ## checks if uv.lock is up-to-date and manually syncs all deps + extras - uv lock --check +install: venv ## updates uv.lock if needed and manually syncs all deps + extras + uv lock uv sync --extra all clean: ## Remove test and coverage artifacts diff --git a/pyproject.toml b/pyproject.toml index be27ae9c..1c504072 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,9 @@ Repository = "https://github.com/dherincx92/fpds" Issues = "https://github.com/dherincx92/fpds/issues" [project.optional-dependencies] +cli = [ + "tabulate==0.9.0", +] dev = [ "ipdb==0.13.9", "ipython==8.5.0", @@ -44,6 +47,7 @@ packaging = [ "twine==5.1.1", ] all = [ + "fpds[cli]", "fpds[dev]", "fpds[tests]", "fpds[packaging]", diff --git a/src/fpds/cli/__init__.py b/src/fpds/cli/__init__.py index 5ef66f37..61936635 100644 --- a/src/fpds/cli/__init__.py +++ b/src/fpds/cli/__init__.py @@ -3,14 +3,28 @@ import click from .parse import parse as _parse +from .params import params as _params -@click.group() -def cli() -> None: +@click.group(invoke_without_command=True) +@click.pass_context +def cli(ctx): """ CLI for parsing the FPDS ATOM feed found at https://www.fpds.gov/fpdsng_cms/index.php/en/ """ + ascii_art = r""" + __________ ____ _____ + / ____/ __ \/ __ \/ ___/ + / /_ / /_/ / / / /\__ \ + / __/ / ____/ /_/ /___/ / + /_/ /_/ /_____//____/ + """ + click.echo(ascii_art + "\nWelcome to a more user-friendly FPDS 🚀\n") + + if ctx.invoked_subcommand is None: + click.echo(ctx.get_help()) cli.add_command(_parse) +cli.add_command(_params) diff --git a/src/fpds/cli/params.py b/src/fpds/cli/params.py new file mode 100644 index 00000000..25fe5f82 --- /dev/null +++ b/src/fpds/cli/params.py @@ -0,0 +1,55 @@ +import click +import json +import textwrap + +from fpds.config import FPDS_DATA_DIR, FPDS_FIELDS_CONFIG +from tabulate import tabulate + + +@click.option( + "-e", + "--export", + required=False, + type=bool, + help="If True, exports full list of field metadata", +) +@click.command() +def params(export): + """ + Command for displaying available filtering fields for parsing command. + + \b + Usage: + $ fpds params [OPTIONS] + + \b + Options: + -e, --export If True, exports all available metadata for fields. + """ + parameters = FPDS_FIELDS_CONFIG + + if export: + metadata_file = FPDS_DATA_DIR / "fields.json" + with open(metadata_file, "w") as f: + json.dump(parameters, f) + click.echo(click.style(f"Fields metadata exported to {metadata_file}", fg="green")) + return + + message = ( + f"The following {len(parameters)} fields support regex validation. " + "If you wish to see more details about fields, set the --export flag to True. " + "If your field is not listed or if the regex pattern is no longer valid, " + "bypass validation by using the --skip-regex-validation flag. " + "Consult the README.md for examples." + ) + click.echo(click.style("\n".join(textwrap.wrap(message, width=87)), fg="green")) + parameters = FPDS_FIELDS_CONFIG + data = [ + [r['name'], r['description']] for r in parameters + ] + + # Define headers + headers = ["Name", "Description"] + + # Print table with fancy_grid + print(tabulate(data, headers=headers, tablefmt="fancy_grid")) \ No newline at end of file diff --git a/uv.lock b/uv.lock index e9c175e2..dbc9925d 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.11" [[package]] @@ -392,11 +392,15 @@ all = [ { name = "pytest-cov" }, { name = "pytest-runner" }, { name = "ruff" }, + { name = "tabulate" }, { name = "twine" }, { name = "types-tqdm" }, { name = "versioningit" }, { name = "wheel" }, ] +cli = [ + { name = "tabulate" }, +] dev = [ { name = "ipdb" }, { name = "ipython" }, @@ -421,6 +425,7 @@ requires-dist = [ { name = "aiohttp", specifier = ">=3.9.2,<4.0.0" }, { name = "build", marker = "extra == 'packaging'", specifier = "==0.8.0" }, { name = "click", specifier = ">=8.1.3,<9.0.0" }, + { name = "fpds", extras = ["cli"], marker = "extra == 'all'" }, { name = "fpds", extras = ["dev"], marker = "extra == 'all'" }, { name = "fpds", extras = ["packaging"], marker = "extra == 'all'" }, { name = "fpds", extras = ["tests"], marker = "extra == 'all'" }, @@ -431,13 +436,14 @@ requires-dist = [ { name = "pytest-cov", marker = "extra == 'tests'", specifier = "==3.0.0" }, { name = "pytest-runner", marker = "extra == 'tests'", specifier = "==6.0.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = "==0.12.1" }, + { name = "tabulate", marker = "extra == 'cli'", specifier = "==0.9.0" }, { name = "tqdm", specifier = ">=4.67.1,<5.0.0" }, { name = "twine", marker = "extra == 'packaging'", specifier = "==5.1.1" }, { name = "types-tqdm", marker = "extra == 'dev'", specifier = "==4.64.7.9" }, { name = "versioningit", marker = "extra == 'dev'", specifier = ">=3.0.0,<4.0.0" }, { name = "wheel", marker = "extra == 'packaging'", specifier = "==0.37.1" }, ] -provides-extras = ["dev", "tests", "packaging", "all"] +provides-extras = ["cli", "dev", "tests", "packaging", "all"] [[package]] name = "frozenlist" @@ -1215,6 +1221,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, ] +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + [[package]] name = "toml" version = "0.10.2" From dcf4c9756d0b8df46cdb1e548048b5440ef69b8b Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 18 Oct 2025 13:44:22 -0400 Subject: [PATCH 002/108] minor: update CLI with new fields command --- src/fpds/cli/__init__.py | 4 ++-- src/fpds/cli/{params.py => fields.py} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/fpds/cli/{params.py => fields.py} (96%) diff --git a/src/fpds/cli/__init__.py b/src/fpds/cli/__init__.py index 61936635..986f0e4f 100644 --- a/src/fpds/cli/__init__.py +++ b/src/fpds/cli/__init__.py @@ -3,7 +3,7 @@ import click from .parse import parse as _parse -from .params import params as _params +from .fields import fields as _fields @click.group(invoke_without_command=True) @@ -27,4 +27,4 @@ def cli(ctx): cli.add_command(_parse) -cli.add_command(_params) +cli.add_command(_fields) diff --git a/src/fpds/cli/params.py b/src/fpds/cli/fields.py similarity index 96% rename from src/fpds/cli/params.py rename to src/fpds/cli/fields.py index 25fe5f82..e61fd9b2 100644 --- a/src/fpds/cli/params.py +++ b/src/fpds/cli/fields.py @@ -14,13 +14,13 @@ help="If True, exports full list of field metadata", ) @click.command() -def params(export): +def fields(export): """ Command for displaying available filtering fields for parsing command. \b Usage: - $ fpds params [OPTIONS] + $ fpds fields [OPTIONS] \b Options: From b1ae03877d19c1967e84488bb3ae6369b27a77c3 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 18 Oct 2025 13:46:45 -0400 Subject: [PATCH 003/108] Add end of file whitespace --- src/fpds/cli/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpds/cli/fields.py b/src/fpds/cli/fields.py index e61fd9b2..996a7204 100644 --- a/src/fpds/cli/fields.py +++ b/src/fpds/cli/fields.py @@ -52,4 +52,4 @@ def fields(export): headers = ["Name", "Description"] # Print table with fancy_grid - print(tabulate(data, headers=headers, tablefmt="fancy_grid")) \ No newline at end of file + print(tabulate(data, headers=headers, tablefmt="fancy_grid")) From 9dfee86e6def2c9b094bf1a093e1ac300450f169 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 18 Oct 2025 16:32:29 -0400 Subject: [PATCH 004/108] Fix README --- README.md | 59 ++++++++++++++++++++++++++++++++---------- src/fpds/cli/fields.py | 20 +++++++------- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 9cacd14c..6a79519d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,21 @@ # fpds + + __________ ____ _____ + / ____/ __ \/ __ \/ ___/ + / /_ / /_/ / / / /\__ \ + / __/ / ____/ /_/ /___/ / + /_/ /_/ /_____//____/ + + Welcome to a more user-friendly FPDS 🚀 A light-weight, pythonic parser for the Federal Procurement Data System (FPDS) ATOM Feed. Reference [here](https://www.fpds.gov/fpdsng_cms/index.php/en/). ## Motivation -The FPDS ATOM feed limits each request to 10 records, which forces users to deal with pagination. Additonally, data is exported as XML, which proves annoying. `fpds` will handle all pagination and data -transformation to provide users with a nice JSON representation of the -equivalent XML data and attributes. +The FPDS ATOM feed limits each request to 10 records, which forces users to deal +with pagination. Additonally, data is exported as XML, which proves annoying. +`fpds` will handle all pagination and data transformation to provide users with +a nice JSON representation of the equivalent XML data and attributes. ## Setup @@ -16,13 +25,16 @@ _highly_ recommended since this library is tested with it. ### Installing `uv` -You can follow any of the methods found [here](https://docs.astral.sh/uv/getting-started/installation/). If on Linux or MacOS, we recommend using Homebrew: +You can follow any of the methods found [here](https://docs.astral.sh/uv/getting-started/installation/). +If on Linux or MacOS, we recommend using Homebrew: ``` $ brew install uv ``` -Once `uv` is installed, you can use the project Makefile to ensure your local environment is synced with the latest library installation. Start by running `make install` — this will check the status of the `uv.lock` file, and install all project dependencies + extras +Once `uv` is installed, you can use the project Makefile to ensure your local environment +is synced with the latest library installation. Start by running `make install` — this +will check the status of the `uv.lock` file, and install all project dependencies + extras. ### Local Development @@ -46,8 +58,10 @@ $ make local-test ## Usage For a list of valid search criteria parameters, consult FPDS documentation -found [here](https://www.fpds.gov/wiki/index.php/Atom_Feed_Usage). Parameters -will follow the `URL String` format shown in the link above, with the +found [here](https://www.fpds.gov/wiki/index.php/Atom_Feed_Usage). + +### CLI +Parameters will follow the `URL String` format shown in the link above, with the following exceptions: + Colons (:) will be replaced by equal signs (=) @@ -57,24 +71,42 @@ entire criteria string in quotes. For example, `AGENCY_CODE:"3600"` should be used as `"AGENCY_CODE=3600"`. -Via CLI: ``` -$ fpds parse "LAST_MOD_DATE=[2022/01/01, 2022/05/01]" "AGENCY_CODE=7504" +$ uv run fpds parse "LAST_MOD_DATE=[2022/01/01, 2022/05/01]" "AGENCY_CODE=7504" ``` By default, data will be dumped into an `.fpds` folder at the user's `$HOME` directory. If you wish to override this behavior, provide the `-o` option. The directory will be created if it doesn't exist. +``` +$ uv run fpds parse "LAST_MOD_DATE=[2022/01/01, 2022/05/01]" "AGENCY_CODE=7504" -o ~/.my-preferred-dir +``` + As of v1.5.0, you can opt out of regex validation by setting the `-k` flag to `False` — this is helpful in scenarios when either the regex pattern has been altered by the ATOM feed or a new parameter name is supported, but not yet added to the configuration in this library. +_NOTE_: if you use `-k`, you will disable regex validation for all parameters, +which means you will be responsible for handling quoting yourself. This is +intentional. + +``` +$ uv run fpds parse -k "A_NEW_PARAM=a-new-value" +``` + +As of v1.6.0, the `fields` command provides a quick reference of available +filtering fields. To print them out, run the following command. Use the `--export` +flag to export the full metadata for fields (this is helpful if you wish to see +quoting configuration and the regex validation pattern). + ``` -$ fpds parse "LAST_MOD_DATE=[2022/01/01, 2022/05/01]" "AGENCY_CODE=7504" -o ~/.my-preferred-dir +$ uv run fpds fields --export true ``` +### Python + Same request via python interpreter: ``` import asyncio @@ -100,8 +132,8 @@ records = asyncio.run(request.data()) # Highlights -Between v1.2.1 and v1.3.0, significant improvements were made with `asyncio`. Here are some rough benchmarks in estimated data extraction + post-processing -times: +Between v1.2.1 and v1.3.0, significant improvements were made with `asyncio`. Here are +some rough benchmarks in estimated data extraction + post-processing times: | v1.2.1 | v.1.3.0 | -------- | -------- @@ -116,4 +148,5 @@ This equates to a **84.89%** decrease in completion time! # Notes -Please be aware that this project is an after-hours passion of mine. I do my best to accomodate requests the best I can, but I receive no $$$ for any of the work I do here. +Please be aware that this project is an after-hours passion of mine. I do my best +to accomodate requests the best I can, but I receive no $$$ for any of the work I do here. diff --git a/src/fpds/cli/fields.py b/src/fpds/cli/fields.py index 996a7204..bbf6c7f0 100644 --- a/src/fpds/cli/fields.py +++ b/src/fpds/cli/fields.py @@ -1,3 +1,10 @@ +""" +Command for displaying FPDS filtering fields. + +author: derek663@gmail.com +last_updated: 2025-10-18 +""" + import click import json import textwrap @@ -16,7 +23,7 @@ @click.command() def fields(export): """ - Command for displaying available filtering fields for parsing command. + Command for displaying available filtering fields for the parsing command. \b Usage: @@ -43,13 +50,6 @@ def fields(export): "Consult the README.md for examples." ) click.echo(click.style("\n".join(textwrap.wrap(message, width=87)), fg="green")) - parameters = FPDS_FIELDS_CONFIG - data = [ - [r['name'], r['description']] for r in parameters - ] - - # Define headers + data = [[field['name'], field['description']] for field in FPDS_FIELDS_CONFIG] headers = ["Name", "Description"] - - # Print table with fancy_grid - print(tabulate(data, headers=headers, tablefmt="fancy_grid")) + click.echo(tabulate(data, headers=headers, tablefmt="fancy_grid")) From f8cefe7d196e21811758465630ac762ce3f2fa9b Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Wed, 10 Dec 2025 00:00:06 -0500 Subject: [PATCH 005/108] minor: fix field with missing regex pattern and update data method to output records as json gz; first iteration --- pyproject.toml | 8 +- src/fpds/constants/fields.json | 3 +- src/fpds/core/parser.py | 171 +++++++++--- uv.lock | 485 ++++++--------------------------- 4 files changed, 223 insertions(+), 444 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1c504072..b75b9c32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,9 +15,8 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] dependencies = [ - "aiohttp>=3.9.2,<4.0.0", - "click>=8.1.3,<9.0.0", - "tqdm>=4.67.1,<5.0.0", + "httpx==0.28.1", + "typer==0.19.2", ] [project.urls] @@ -34,6 +33,7 @@ dev = [ "ruff==0.12.1", "mypy>=0.910", "types-tqdm==4.64.7.9", + "types-tabulate==0.9.0.20241207", "versioningit>=3.0.0,<4.0.0", ] tests = [ @@ -54,7 +54,7 @@ all = [ ] [project.scripts] -fpds = "fpds.cli:cli" +fpds = "fpds.cli.root:app" [tool.pytest.ini_options] pythonpath = ["."] diff --git a/src/fpds/constants/fields.json b/src/fpds/constants/fields.json index 5b536917..556f4802 100644 --- a/src/fpds/constants/fields.json +++ b/src/fpds/constants/fields.json @@ -392,7 +392,8 @@ { "description": "PSC Type", "name": "PRODUCT_OR_SERVICE_TYPE", - "quotes": true + "quotes": true, + "regex": ".*" }, { "description": "Reason for Modification", diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index 8f742e95..a1b1932d 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -7,16 +7,27 @@ """ import asyncio +import gzip +import json import multiprocessing +from pathlib import Path import warnings from asyncio import Semaphore from concurrent.futures import ProcessPoolExecutor from typing import AsyncGenerator, List, Optional from urllib import parse from urllib.request import urlopen - -from aiohttp import ClientSession -from tqdm import tqdm +from uuid import uuid4 + +from httpx import AsyncClient +from rich.progress import TaskID +from rich.progress import ( + BarColumn, + Progress, + SpinnerColumn, + TextColumn, + TimeRemainingColumn, +) from fpds.core import FPDS_ENTRY from fpds.core.mixins import fpdsMixin @@ -24,6 +35,8 @@ from fpds.errors import fpdsMaxPageLengthExceededError, fpdsMissingKeywordParameterError from fpds.utilities import validate_kwarg +from fpds.config import FPDS_DATA_DATE_DIR + class fpdsRequest(fpdsMixin): """Makes a GET request to the FPDS ATOM feed. @@ -55,6 +68,9 @@ class fpdsRequest(fpdsMixin): thread_count: `int` Defaults to 10. The number of threads to send per search. + max_chunk_size_mb: `int` + Defaults to 100. + The maximum size of each outputted data file (uncompressed). page: `Optional[int]` Defaults to `None`. The page of results to retrieve. @@ -84,14 +100,16 @@ def __init__( cli_run: bool = False, skip_regex_validation: bool = False, thread_count: int = 10, + max_chunk_size_mb: int = 100, page: Optional[int] = None, **kwargs: str, ) -> None: self.cli_run = cli_run self.skip_regex_validation = skip_regex_validation self.thread_count = thread_count + self.max_chunk_size_mb = max_chunk_size_mb self.page = page - self.links = [] # type: List[str] + self.links: List[str] = [] if kwargs: self.kwargs = kwargs @@ -144,24 +162,44 @@ def initial_request(self) -> bytes: content_tree = response.read() return content_tree - async def convert(self, session: ClientSession, link: str) -> fpdsSubTree: + async def convert( + self, + client: AsyncClient, + link: str, + semaphore: asyncio.Semaphore, + ) -> fpdsSubTree: """Retrieves content from FPDS ATOM feed as a SubTree instance.""" - async with session.get(link) as response: - content = await response.read() - subtree = fpdsSubTree(content=content) + async with semaphore: + response = await client.get(link) + subtree = fpdsSubTree(content=response.content) return subtree async def fetch(self) -> List[fpdsSubTree]: """Asynchronously parses all ATOM feed pages for current request.""" - semaphore = Semaphore(self.thread_count) - if not self.links: return [] - - async with semaphore: - async with ClientSession() as session: - tasks = [self.convert(session, link) for link in self.links] - return await asyncio.gather(*tasks) + semaphore = asyncio.Semaphore(self.thread_count) + + async def convert_with_progress( + client: AsyncClient, + link: str, + semaphore: asyncio.Semaphore, + progress: Progress, + task_id: TaskID, + ) -> fpdsSubTree: + result = await self.convert(client=client, link=link, semaphore=semaphore) + progress.update(task_id=task_id, advance=1) + return result + + with self._create_progress() as progress: + task_id = progress.add_task("Fetching data...", total=len(self.links)) + async with AsyncClient(timeout=None) as client: + tasks = [ + convert_with_progress(client, link, semaphore, progress, task_id) + for link in self.links + ] + results = await asyncio.gather(*tasks) + return results def page_index(self) -> Optional[int]: """Converts `page` to index integer.""" @@ -170,6 +208,18 @@ def page_index(self) -> Optional[int]: idx = 0 if self.page == 1 else self.page - 1 return idx + @staticmethod + def _create_progress() -> Progress: + """Creates a Progress instance with standard configuration.""" + return Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), + TextColumn("({task.completed}/{task.total})"), + TimeRemainingColumn(elapsed_when_finished=True), + ) + @staticmethod def _jsonify(entry: fpdsSubTree) -> List[FPDS_ENTRY]: """Wrapper around `jsonify` method for avoiding pickle issue.""" @@ -183,33 +233,84 @@ async def iter_data(self) -> AsyncGenerator[FPDS_ENTRY, None]: `FPDS_ENTRY` A single FPDS record as it becomes available. """ + from concurrent.futures import as_completed + num_processes = multiprocessing.cpu_count() data = await self.fetch() # List[fpdsSubTree] with ProcessPoolExecutor(max_workers=num_processes) as pool: - with tqdm(total=len(data)) as progress: - futures = [] - - # allows tqdm progress bar to display correctly - for record in data: - future = pool.submit(self._jsonify, record) - future.add_done_callback(lambda p: progress.update()) - futures.append(future) - - for future in futures: + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), + TextColumn("({task.completed}/{task.total})"), + TimeRemainingColumn(elapsed_when_finished=True), + ) as progress: + task_id = progress.add_task( + "Processing records...", total=len(data) + ) + + # Submit all tasks + future_to_record = { + pool.submit(self._jsonify, record): record for record in data + } + + # Process results as they complete and yield immediately + for future in as_completed(future_to_record): + progress.update(task_id, advance=1) result = future.result() for entry in result: yield entry - async def data(self) -> List[FPDS_ENTRY]: - """Collects all FPDS records into a list. - Returns - ------- - records: `List[FPDS_ENTRY]` - FPDS records as a list of dictionaries with de-nested XML attributes. - """ - records = [] + async def data( + self, + output_dir: Path = FPDS_DATA_DATE_DIR, + ) -> None: + run_id = str(uuid4()) + + # Memory-efficient chunking: write to gzip files + output_path = (Path(output_dir) / run_id).expanduser() + output_path.mkdir(parents=True, exist_ok=True) + + max_bytes = self.max_chunk_size_mb * 1_048_576 # Convert MB to bytes + file_paths: List[str] = [] + chunk_buffer: List[FPDS_ENTRY] = [] + current_size = 0 + chunk_index = 0 + async for entry in self.iter_data(): - records.append(entry) - return records + # Convert entry to JSON string to measure size + entry_json = json.dumps(entry) + entry_size = len(entry_json.encode("utf-8")) + + # Check if adding this entry would exceed the chunk size + if current_size + entry_size > max_bytes and chunk_buffer: + # Write current buffer to gzip file + file_path = output_path / f"{uuid4()}.json.gz" + file_paths.append(str(file_path)) + + with gzip.open(file_path, "wt", encoding="utf-8") as gz_file: + json.dump(chunk_buffer, gz_file) + + # Reset buffer for next chunk + chunk_buffer = [entry] + current_size = entry_size + chunk_index += 1 + else: + # Add entry to current buffer + chunk_buffer.append(entry) + current_size += entry_size + + # Write final chunk if there are remaining records + + if chunk_buffer: + file_path = output_path / f"{uuid4()}.json.gz" + file_paths.append(str(file_path)) + + with gzip.open(file_path, "wt", encoding="utf-8") as gz_file: + json.dump(chunk_buffer, gz_file) + + + print(f"Wrote {len(file_paths)} chunks to {output_path}") diff --git a/uv.lock b/uv.lock index dbc9925d..175b3402 100644 --- a/uv.lock +++ b/uv.lock @@ -1,91 +1,18 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.11" [[package]] -name = "aiohappyeyeballs" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, -] - -[[package]] -name = "aiohttp" -version = "3.11.18" +name = "anyio" +version = "4.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/e7/fa1a8c00e2c54b05dc8cb5d1439f627f7c267874e3f7bb047146116020f9/aiohttp-3.11.18.tar.gz", hash = "sha256:ae856e1138612b7e412db63b7708735cff4d38d0399f6a5435d3dac2669f558a", size = 7678653, upload-time = "2025-04-21T09:43:09.191Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/10/fd9ee4f9e042818c3c2390054c08ccd34556a3cb209d83285616434cf93e/aiohttp-3.11.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:427fdc56ccb6901ff8088544bde47084845ea81591deb16f957897f0f0ba1be9", size = 712088, upload-time = "2025-04-21T09:40:55.776Z" }, - { url = "https://files.pythonhosted.org/packages/22/eb/6a77f055ca56f7aae2cd2a5607a3c9e7b9554f1497a069dcfcb52bfc9540/aiohttp-3.11.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c828b6d23b984255b85b9b04a5b963a74278b7356a7de84fda5e3b76866597b", size = 471450, upload-time = "2025-04-21T09:40:57.301Z" }, - { url = "https://files.pythonhosted.org/packages/78/dc/5f3c0d27c91abf0bb5d103e9c9b0ff059f60cf6031a5f06f456c90731f42/aiohttp-3.11.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c2eaa145bb36b33af1ff2860820ba0589e165be4ab63a49aebfd0981c173b66", size = 457836, upload-time = "2025-04-21T09:40:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/49/7b/55b65af9ef48b9b811c91ff8b5b9de9650c71147f10523e278d297750bc8/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d518ce32179f7e2096bf4e3e8438cf445f05fedd597f252de9f54c728574756", size = 1690978, upload-time = "2025-04-21T09:41:00.795Z" }, - { url = "https://files.pythonhosted.org/packages/a2/5a/3f8938c4f68ae400152b42742653477fc625d6bfe02e764f3521321c8442/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700055a6e05c2f4711011a44364020d7a10fbbcd02fbf3e30e8f7e7fddc8717", size = 1745307, upload-time = "2025-04-21T09:41:02.89Z" }, - { url = "https://files.pythonhosted.org/packages/b4/42/89b694a293333ef6f771c62da022163bcf44fb03d4824372d88e3dc12530/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bd1cde83e4684324e6ee19adfc25fd649d04078179890be7b29f76b501de8e4", size = 1780692, upload-time = "2025-04-21T09:41:04.461Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ce/1a75384e01dd1bf546898b6062b1b5f7a59b6692ef802e4dd6db64fed264/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b8870fe1c9a201b8c0d12c94fe781b918664766728783241a79e0468427e4f", size = 1676934, upload-time = "2025-04-21T09:41:06.728Z" }, - { url = "https://files.pythonhosted.org/packages/a5/31/442483276e6c368ab5169797d9873b5875213cbcf7e74b95ad1c5003098a/aiohttp-3.11.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25557982dd36b9e32c0a3357f30804e80790ec2c4d20ac6bcc598533e04c6361", size = 1621190, upload-time = "2025-04-21T09:41:08.293Z" }, - { url = "https://files.pythonhosted.org/packages/7b/83/90274bf12c079457966008a58831a99675265b6a34b505243e004b408934/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e889c9df381a2433802991288a61e5a19ceb4f61bd14f5c9fa165655dcb1fd1", size = 1658947, upload-time = "2025-04-21T09:41:11.054Z" }, - { url = "https://files.pythonhosted.org/packages/91/c1/da9cee47a0350b78fdc93670ebe7ad74103011d7778ab4c382ca4883098d/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9ea345fda05bae217b6cce2acf3682ce3b13d0d16dd47d0de7080e5e21362421", size = 1654443, upload-time = "2025-04-21T09:41:13.213Z" }, - { url = "https://files.pythonhosted.org/packages/c9/f2/73cbe18dc25d624f79a09448adfc4972f82ed6088759ddcf783cd201956c/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9f26545b9940c4b46f0a9388fd04ee3ad7064c4017b5a334dd450f616396590e", size = 1644169, upload-time = "2025-04-21T09:41:14.827Z" }, - { url = "https://files.pythonhosted.org/packages/5b/32/970b0a196c4dccb1b0cfa5b4dc3b20f63d76f1c608f41001a84b2fd23c3d/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3a621d85e85dccabd700294494d7179ed1590b6d07a35709bb9bd608c7f5dd1d", size = 1728532, upload-time = "2025-04-21T09:41:17.168Z" }, - { url = "https://files.pythonhosted.org/packages/0b/50/b1dc810a41918d2ea9574e74125eb053063bc5e14aba2d98966f7d734da0/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c23fd8d08eb9c2af3faeedc8c56e134acdaf36e2117ee059d7defa655130e5f", size = 1750310, upload-time = "2025-04-21T09:41:19.353Z" }, - { url = "https://files.pythonhosted.org/packages/95/24/39271f5990b35ff32179cc95537e92499d3791ae82af7dcf562be785cd15/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9e6b0e519067caa4fd7fb72e3e8002d16a68e84e62e7291092a5433763dc0dd", size = 1691580, upload-time = "2025-04-21T09:41:21.868Z" }, - { url = "https://files.pythonhosted.org/packages/6b/78/75d0353feb77f041460564f12fe58e456436bbc00cbbf5d676dbf0038cc2/aiohttp-3.11.18-cp311-cp311-win32.whl", hash = "sha256:122f3e739f6607e5e4c6a2f8562a6f476192a682a52bda8b4c6d4254e1138f4d", size = 417565, upload-time = "2025-04-21T09:41:24.78Z" }, - { url = "https://files.pythonhosted.org/packages/ed/97/b912dcb654634a813f8518de359364dfc45976f822116e725dc80a688eee/aiohttp-3.11.18-cp311-cp311-win_amd64.whl", hash = "sha256:e6f3c0a3a1e73e88af384b2e8a0b9f4fb73245afd47589df2afcab6b638fa0e6", size = 443652, upload-time = "2025-04-21T09:41:26.48Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d2/5bc436f42bf4745c55f33e1e6a2d69e77075d3e768e3d1a34f96ee5298aa/aiohttp-3.11.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63d71eceb9cad35d47d71f78edac41fcd01ff10cacaa64e473d1aec13fa02df2", size = 706671, upload-time = "2025-04-21T09:41:28.021Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d0/2dbabecc4e078c0474abb40536bbde717fb2e39962f41c5fc7a216b18ea7/aiohttp-3.11.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d1929da615840969929e8878d7951b31afe0bac883d84418f92e5755d7b49508", size = 466169, upload-time = "2025-04-21T09:41:29.783Z" }, - { url = "https://files.pythonhosted.org/packages/70/84/19edcf0b22933932faa6e0be0d933a27bd173da02dc125b7354dff4d8da4/aiohttp-3.11.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d0aebeb2392f19b184e3fdd9e651b0e39cd0f195cdb93328bd124a1d455cd0e", size = 457554, upload-time = "2025-04-21T09:41:31.327Z" }, - { url = "https://files.pythonhosted.org/packages/32/d0/e8d1f034ae5624a0f21e4fb3feff79342ce631f3a4d26bd3e58b31ef033b/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3849ead845e8444f7331c284132ab314b4dac43bfae1e3cf350906d4fff4620f", size = 1690154, upload-time = "2025-04-21T09:41:33.541Z" }, - { url = "https://files.pythonhosted.org/packages/16/de/2f9dbe2ac6f38f8495562077131888e0d2897e3798a0ff3adda766b04a34/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e8452ad6b2863709f8b3d615955aa0807bc093c34b8e25b3b52097fe421cb7f", size = 1733402, upload-time = "2025-04-21T09:41:35.634Z" }, - { url = "https://files.pythonhosted.org/packages/e0/04/bd2870e1e9aef990d14b6df2a695f17807baf5c85a4c187a492bda569571/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b8d2b42073611c860a37f718b3d61ae8b4c2b124b2e776e2c10619d920350ec", size = 1783958, upload-time = "2025-04-21T09:41:37.456Z" }, - { url = "https://files.pythonhosted.org/packages/23/06/4203ffa2beb5bedb07f0da0f79b7d9039d1c33f522e0d1a2d5b6218e6f2e/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fbf91f6a0ac317c0a07eb328a1384941872f6761f2e6f7208b63c4cc0a7ff6", size = 1695288, upload-time = "2025-04-21T09:41:39.756Z" }, - { url = "https://files.pythonhosted.org/packages/30/b2/e2285dda065d9f29ab4b23d8bcc81eb881db512afb38a3f5247b191be36c/aiohttp-3.11.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ff5625413fec55216da5eaa011cf6b0a2ed67a565914a212a51aa3755b0009", size = 1618871, upload-time = "2025-04-21T09:41:41.972Z" }, - { url = "https://files.pythonhosted.org/packages/57/e0/88f2987885d4b646de2036f7296ebea9268fdbf27476da551c1a7c158bc0/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f33a92a2fde08e8c6b0c61815521324fc1612f397abf96eed86b8e31618fdb4", size = 1646262, upload-time = "2025-04-21T09:41:44.192Z" }, - { url = "https://files.pythonhosted.org/packages/e0/19/4d2da508b4c587e7472a032290b2981f7caeca82b4354e19ab3df2f51d56/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:11d5391946605f445ddafda5eab11caf310f90cdda1fd99865564e3164f5cff9", size = 1677431, upload-time = "2025-04-21T09:41:46.049Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ae/047473ea50150a41440f3265f53db1738870b5a1e5406ece561ca61a3bf4/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3cc314245deb311364884e44242e00c18b5896e4fe6d5f942e7ad7e4cb640adb", size = 1637430, upload-time = "2025-04-21T09:41:47.973Z" }, - { url = "https://files.pythonhosted.org/packages/11/32/c6d1e3748077ce7ee13745fae33e5cb1dac3e3b8f8787bf738a93c94a7d2/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f421843b0f70740772228b9e8093289924359d306530bcd3926f39acbe1adda", size = 1703342, upload-time = "2025-04-21T09:41:50.323Z" }, - { url = "https://files.pythonhosted.org/packages/c5/1d/a3b57bfdbe285f0d45572d6d8f534fd58761da3e9cbc3098372565005606/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e220e7562467dc8d589e31c1acd13438d82c03d7f385c9cd41a3f6d1d15807c1", size = 1740600, upload-time = "2025-04-21T09:41:52.111Z" }, - { url = "https://files.pythonhosted.org/packages/a5/71/f9cd2fed33fa2b7ce4d412fb7876547abb821d5b5520787d159d0748321d/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ab2ef72f8605046115bc9aa8e9d14fd49086d405855f40b79ed9e5c1f9f4faea", size = 1695131, upload-time = "2025-04-21T09:41:53.94Z" }, - { url = "https://files.pythonhosted.org/packages/97/97/d1248cd6d02b9de6aa514793d0dcb20099f0ec47ae71a933290116c070c5/aiohttp-3.11.18-cp312-cp312-win32.whl", hash = "sha256:12a62691eb5aac58d65200c7ae94d73e8a65c331c3a86a2e9670927e94339ee8", size = 412442, upload-time = "2025-04-21T09:41:55.689Z" }, - { url = "https://files.pythonhosted.org/packages/33/9a/e34e65506e06427b111e19218a99abf627638a9703f4b8bcc3e3021277ed/aiohttp-3.11.18-cp312-cp312-win_amd64.whl", hash = "sha256:364329f319c499128fd5cd2d1c31c44f234c58f9b96cc57f743d16ec4f3238c8", size = 439444, upload-time = "2025-04-21T09:41:57.977Z" }, - { url = "https://files.pythonhosted.org/packages/0a/18/be8b5dd6b9cf1b2172301dbed28e8e5e878ee687c21947a6c81d6ceaa15d/aiohttp-3.11.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:474215ec618974054cf5dc465497ae9708543cbfc312c65212325d4212525811", size = 699833, upload-time = "2025-04-21T09:42:00.298Z" }, - { url = "https://files.pythonhosted.org/packages/0d/84/ecdc68e293110e6f6f6d7b57786a77555a85f70edd2b180fb1fafaff361a/aiohttp-3.11.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ced70adf03920d4e67c373fd692123e34d3ac81dfa1c27e45904a628567d804", size = 462774, upload-time = "2025-04-21T09:42:02.015Z" }, - { url = "https://files.pythonhosted.org/packages/d7/85/f07718cca55884dad83cc2433746384d267ee970e91f0dcc75c6d5544079/aiohttp-3.11.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d9f6c0152f8d71361905aaf9ed979259537981f47ad099c8b3d81e0319814bd", size = 454429, upload-time = "2025-04-21T09:42:03.728Z" }, - { url = "https://files.pythonhosted.org/packages/82/02/7f669c3d4d39810db8842c4e572ce4fe3b3a9b82945fdd64affea4c6947e/aiohttp-3.11.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a35197013ed929c0aed5c9096de1fc5a9d336914d73ab3f9df14741668c0616c", size = 1670283, upload-time = "2025-04-21T09:42:06.053Z" }, - { url = "https://files.pythonhosted.org/packages/ec/79/b82a12f67009b377b6c07a26bdd1b81dab7409fc2902d669dbfa79e5ac02/aiohttp-3.11.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:540b8a1f3a424f1af63e0af2d2853a759242a1769f9f1ab053996a392bd70118", size = 1717231, upload-time = "2025-04-21T09:42:07.953Z" }, - { url = "https://files.pythonhosted.org/packages/a6/38/d5a1f28c3904a840642b9a12c286ff41fc66dfa28b87e204b1f242dbd5e6/aiohttp-3.11.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9e6710ebebfce2ba21cee6d91e7452d1125100f41b906fb5af3da8c78b764c1", size = 1769621, upload-time = "2025-04-21T09:42:09.855Z" }, - { url = "https://files.pythonhosted.org/packages/53/2d/deb3749ba293e716b5714dda06e257f123c5b8679072346b1eb28b766a0b/aiohttp-3.11.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8af2ef3b4b652ff109f98087242e2ab974b2b2b496304063585e3d78de0b000", size = 1678667, upload-time = "2025-04-21T09:42:11.741Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a8/04b6e11683a54e104b984bd19a9790eb1ae5f50968b601bb202d0406f0ff/aiohttp-3.11.18-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28c3f975e5ae3dbcbe95b7e3dcd30e51da561a0a0f2cfbcdea30fc1308d72137", size = 1601592, upload-time = "2025-04-21T09:42:14.137Z" }, - { url = "https://files.pythonhosted.org/packages/5e/9d/c33305ae8370b789423623f0e073d09ac775cd9c831ac0f11338b81c16e0/aiohttp-3.11.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c28875e316c7b4c3e745172d882d8a5c835b11018e33432d281211af35794a93", size = 1621679, upload-time = "2025-04-21T09:42:16.056Z" }, - { url = "https://files.pythonhosted.org/packages/56/45/8e9a27fff0538173d47ba60362823358f7a5f1653c6c30c613469f94150e/aiohttp-3.11.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:13cd38515568ae230e1ef6919e2e33da5d0f46862943fcda74e7e915096815f3", size = 1656878, upload-time = "2025-04-21T09:42:18.368Z" }, - { url = "https://files.pythonhosted.org/packages/84/5b/8c5378f10d7a5a46b10cb9161a3aac3eeae6dba54ec0f627fc4ddc4f2e72/aiohttp-3.11.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0e2a92101efb9f4c2942252c69c63ddb26d20f46f540c239ccfa5af865197bb8", size = 1620509, upload-time = "2025-04-21T09:42:20.141Z" }, - { url = "https://files.pythonhosted.org/packages/9e/2f/99dee7bd91c62c5ff0aa3c55f4ae7e1bc99c6affef780d7777c60c5b3735/aiohttp-3.11.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6d3e32b8753c8d45ac550b11a1090dd66d110d4ef805ffe60fa61495360b3b2", size = 1680263, upload-time = "2025-04-21T09:42:21.993Z" }, - { url = "https://files.pythonhosted.org/packages/03/0a/378745e4ff88acb83e2d5c884a4fe993a6e9f04600a4560ce0e9b19936e3/aiohttp-3.11.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ea4cf2488156e0f281f93cc2fd365025efcba3e2d217cbe3df2840f8c73db261", size = 1715014, upload-time = "2025-04-21T09:42:23.87Z" }, - { url = "https://files.pythonhosted.org/packages/f6/0b/b5524b3bb4b01e91bc4323aad0c2fcaebdf2f1b4d2eb22743948ba364958/aiohttp-3.11.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d4df95ad522c53f2b9ebc07f12ccd2cb15550941e11a5bbc5ddca2ca56316d7", size = 1666614, upload-time = "2025-04-21T09:42:25.764Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b7/3d7b036d5a4ed5a4c704e0754afe2eef24a824dfab08e6efbffb0f6dd36a/aiohttp-3.11.18-cp313-cp313-win32.whl", hash = "sha256:cdd1bbaf1e61f0d94aced116d6e95fe25942f7a5f42382195fd9501089db5d78", size = 411358, upload-time = "2025-04-21T09:42:27.558Z" }, - { url = "https://files.pythonhosted.org/packages/1e/3c/143831b32cd23b5263a995b2a1794e10aa42f8a895aae5074c20fda36c07/aiohttp-3.11.18-cp313-cp313-win_amd64.whl", hash = "sha256:bdd619c27e44382cf642223f11cfd4d795161362a5a1fc1fa3940397bc89db01", size = 437658, upload-time = "2025-04-21T09:42:29.209Z" }, -] - -[[package]] -name = "aiosignal" -version = "1.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "frozenlist" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424, upload-time = "2024-12-13T17:10:40.86Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, ] [[package]] @@ -377,9 +304,8 @@ wheels = [ name = "fpds" source = { editable = "." } dependencies = [ - { name = "aiohttp" }, - { name = "click" }, - { name = "tqdm" }, + { name = "httpx" }, + { name = "typer" }, ] [package.optional-dependencies] @@ -394,6 +320,7 @@ all = [ { name = "ruff" }, { name = "tabulate" }, { name = "twine" }, + { name = "types-tabulate" }, { name = "types-tqdm" }, { name = "versioningit" }, { name = "wheel" }, @@ -406,6 +333,7 @@ dev = [ { name = "ipython" }, { name = "mypy" }, { name = "ruff" }, + { name = "types-tabulate" }, { name = "types-tqdm" }, { name = "versioningit" }, ] @@ -422,13 +350,12 @@ tests = [ [package.metadata] requires-dist = [ - { name = "aiohttp", specifier = ">=3.9.2,<4.0.0" }, { name = "build", marker = "extra == 'packaging'", specifier = "==0.8.0" }, - { name = "click", specifier = ">=8.1.3,<9.0.0" }, { name = "fpds", extras = ["cli"], marker = "extra == 'all'" }, { name = "fpds", extras = ["dev"], marker = "extra == 'all'" }, { name = "fpds", extras = ["packaging"], marker = "extra == 'all'" }, { name = "fpds", extras = ["tests"], marker = "extra == 'all'" }, + { name = "httpx", specifier = "==0.28.1" }, { name = "ipdb", marker = "extra == 'dev'", specifier = "==0.13.9" }, { name = "ipython", marker = "extra == 'dev'", specifier = "==8.5.0" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=0.910" }, @@ -437,8 +364,9 @@ requires-dist = [ { name = "pytest-runner", marker = "extra == 'tests'", specifier = "==6.0.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = "==0.12.1" }, { name = "tabulate", marker = "extra == 'cli'", specifier = "==0.9.0" }, - { name = "tqdm", specifier = ">=4.67.1,<5.0.0" }, { name = "twine", marker = "extra == 'packaging'", specifier = "==5.1.1" }, + { name = "typer", specifier = "==0.19.2" }, + { name = "types-tabulate", marker = "extra == 'dev'", specifier = "==0.9.0.20241207" }, { name = "types-tqdm", marker = "extra == 'dev'", specifier = "==4.64.7.9" }, { name = "versioningit", marker = "extra == 'dev'", specifier = ">=3.0.0,<4.0.0" }, { name = "wheel", marker = "extra == 'packaging'", specifier = "==0.37.1" }, @@ -446,80 +374,40 @@ requires-dist = [ provides-extras = ["cli", "dev", "tests", "packaging", "all"] [[package]] -name = "frozenlist" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/f4/d744cba2da59b5c1d88823cf9e8a6c74e4659e2b27604ed973be2a0bf5ab/frozenlist-1.6.0.tar.gz", hash = "sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68", size = 42831, upload-time = "2025-04-17T22:38:53.099Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/53/b5/bc883b5296ec902115c00be161da93bf661199c465ec4c483feec6ea4c32/frozenlist-1.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae8337990e7a45683548ffb2fee1af2f1ed08169284cd829cdd9a7fa7470530d", size = 160912, upload-time = "2025-04-17T22:36:17.235Z" }, - { url = "https://files.pythonhosted.org/packages/6f/93/51b058b563d0704b39c56baa222828043aafcac17fd3734bec5dbeb619b1/frozenlist-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c952f69dd524558694818a461855f35d36cc7f5c0adddce37e962c85d06eac0", size = 124315, upload-time = "2025-04-17T22:36:18.735Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e0/46cd35219428d350558b874d595e132d1c17a9471a1bd0d01d518a261e7c/frozenlist-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f5fef13136c4e2dee91bfb9a44e236fff78fc2cd9f838eddfc470c3d7d90afe", size = 122230, upload-time = "2025-04-17T22:36:20.6Z" }, - { url = "https://files.pythonhosted.org/packages/d1/0f/7ad2ce928ad06d6dd26a61812b959ded573d3e9d0ee6109d96c2be7172e9/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:716bbba09611b4663ecbb7cd022f640759af8259e12a6ca939c0a6acd49eedba", size = 314842, upload-time = "2025-04-17T22:36:22.088Z" }, - { url = "https://files.pythonhosted.org/packages/34/76/98cbbd8a20a5c3359a2004ae5e5b216af84a150ccbad67c8f8f30fb2ea91/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7b8c4dc422c1a3ffc550b465090e53b0bf4839047f3e436a34172ac67c45d595", size = 304919, upload-time = "2025-04-17T22:36:24.247Z" }, - { url = "https://files.pythonhosted.org/packages/9a/fa/258e771ce3a44348c05e6b01dffc2bc67603fba95761458c238cd09a2c77/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b11534872256e1666116f6587a1592ef395a98b54476addb5e8d352925cb5d4a", size = 324074, upload-time = "2025-04-17T22:36:26.291Z" }, - { url = "https://files.pythonhosted.org/packages/d5/a4/047d861fd8c538210e12b208c0479912273f991356b6bdee7ea8356b07c9/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c6eceb88aaf7221f75be6ab498dc622a151f5f88d536661af3ffc486245a626", size = 321292, upload-time = "2025-04-17T22:36:27.909Z" }, - { url = "https://files.pythonhosted.org/packages/c0/25/cfec8af758b4525676cabd36efcaf7102c1348a776c0d1ad046b8a7cdc65/frozenlist-1.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62c828a5b195570eb4b37369fcbbd58e96c905768d53a44d13044355647838ff", size = 301569, upload-time = "2025-04-17T22:36:29.448Z" }, - { url = "https://files.pythonhosted.org/packages/87/2f/0c819372fa9f0c07b153124bf58683b8d0ca7bb73ea5ccde9b9ef1745beb/frozenlist-1.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c6bd2c6399920c9622362ce95a7d74e7f9af9bfec05fff91b8ce4b9647845a", size = 313625, upload-time = "2025-04-17T22:36:31.55Z" }, - { url = "https://files.pythonhosted.org/packages/50/5f/f0cf8b0fdedffdb76b3745aa13d5dbe404d63493cc211ce8250f2025307f/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49ba23817781e22fcbd45fd9ff2b9b8cdb7b16a42a4851ab8025cae7b22e96d0", size = 312523, upload-time = "2025-04-17T22:36:33.078Z" }, - { url = "https://files.pythonhosted.org/packages/e1/6c/38c49108491272d3e84125bbabf2c2d0b304899b52f49f0539deb26ad18d/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:431ef6937ae0f853143e2ca67d6da76c083e8b1fe3df0e96f3802fd37626e606", size = 322657, upload-time = "2025-04-17T22:36:34.688Z" }, - { url = "https://files.pythonhosted.org/packages/bd/4b/3bd3bad5be06a9d1b04b1c22be80b5fe65b502992d62fab4bdb25d9366ee/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9d124b38b3c299ca68433597ee26b7819209cb8a3a9ea761dfe9db3a04bba584", size = 303414, upload-time = "2025-04-17T22:36:36.363Z" }, - { url = "https://files.pythonhosted.org/packages/5b/89/7e225a30bef6e85dbfe22622c24afe932e9444de3b40d58b1ea589a14ef8/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:118e97556306402e2b010da1ef21ea70cb6d6122e580da64c056b96f524fbd6a", size = 320321, upload-time = "2025-04-17T22:36:38.16Z" }, - { url = "https://files.pythonhosted.org/packages/22/72/7e3acef4dd9e86366cb8f4d8f28e852c2b7e116927e9722b31a6f71ea4b0/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb3b309f1d4086b5533cf7bbcf3f956f0ae6469664522f1bde4feed26fba60f1", size = 323975, upload-time = "2025-04-17T22:36:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/d8/85/e5da03d20507e13c66ce612c9792b76811b7a43e3320cce42d95b85ac755/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54dece0d21dce4fdb188a1ffc555926adf1d1c516e493c2914d7c370e454bc9e", size = 316553, upload-time = "2025-04-17T22:36:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/ac/8e/6c609cbd0580ae8a0661c408149f196aade7d325b1ae7adc930501b81acb/frozenlist-1.6.0-cp311-cp311-win32.whl", hash = "sha256:654e4ba1d0b2154ca2f096bed27461cf6160bc7f504a7f9a9ef447c293caf860", size = 115511, upload-time = "2025-04-17T22:36:44.067Z" }, - { url = "https://files.pythonhosted.org/packages/f2/13/a84804cfde6de12d44ed48ecbf777ba62b12ff09e761f76cdd1ff9e14bb1/frozenlist-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e911391bffdb806001002c1f860787542f45916c3baf764264a52765d5a5603", size = 120863, upload-time = "2025-04-17T22:36:45.465Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8a/289b7d0de2fbac832ea80944d809759976f661557a38bb8e77db5d9f79b7/frozenlist-1.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c5b9e42ace7d95bf41e19b87cec8f262c41d3510d8ad7514ab3862ea2197bfb1", size = 160193, upload-time = "2025-04-17T22:36:47.382Z" }, - { url = "https://files.pythonhosted.org/packages/19/80/2fd17d322aec7f430549f0669f599997174f93ee17929ea5b92781ec902c/frozenlist-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ca9973735ce9f770d24d5484dcb42f68f135351c2fc81a7a9369e48cf2998a29", size = 123831, upload-time = "2025-04-17T22:36:49.401Z" }, - { url = "https://files.pythonhosted.org/packages/99/06/f5812da431273f78c6543e0b2f7de67dfd65eb0a433978b2c9c63d2205e4/frozenlist-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ac40ec76041c67b928ca8aaffba15c2b2ee3f5ae8d0cb0617b5e63ec119ca25", size = 121862, upload-time = "2025-04-17T22:36:51.899Z" }, - { url = "https://files.pythonhosted.org/packages/d0/31/9e61c6b5fc493cf24d54881731204d27105234d09878be1a5983182cc4a5/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b7a8a3180dfb280eb044fdec562f9b461614c0ef21669aea6f1d3dac6ee576", size = 316361, upload-time = "2025-04-17T22:36:53.402Z" }, - { url = "https://files.pythonhosted.org/packages/9d/55/22ca9362d4f0222324981470fd50192be200154d51509ee6eb9baa148e96/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c444d824e22da6c9291886d80c7d00c444981a72686e2b59d38b285617cb52c8", size = 307115, upload-time = "2025-04-17T22:36:55.016Z" }, - { url = "https://files.pythonhosted.org/packages/ae/39/4fff42920a57794881e7bb3898dc7f5f539261711ea411b43bba3cde8b79/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb52c8166499a8150bfd38478248572c924c003cbb45fe3bcd348e5ac7c000f9", size = 322505, upload-time = "2025-04-17T22:36:57.12Z" }, - { url = "https://files.pythonhosted.org/packages/55/f2/88c41f374c1e4cf0092a5459e5f3d6a1e17ed274c98087a76487783df90c/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b35298b2db9c2468106278537ee529719228950a5fdda686582f68f247d1dc6e", size = 322666, upload-time = "2025-04-17T22:36:58.735Z" }, - { url = "https://files.pythonhosted.org/packages/75/51/034eeb75afdf3fd03997856195b500722c0b1a50716664cde64e28299c4b/frozenlist-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d108e2d070034f9d57210f22fefd22ea0d04609fc97c5f7f5a686b3471028590", size = 302119, upload-time = "2025-04-17T22:37:00.512Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a6/564ecde55ee633270a793999ef4fd1d2c2b32b5a7eec903b1012cb7c5143/frozenlist-1.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1be9111cb6756868ac242b3c2bd1f09d9aea09846e4f5c23715e7afb647103", size = 316226, upload-time = "2025-04-17T22:37:02.102Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c8/6c0682c32377f402b8a6174fb16378b683cf6379ab4d2827c580892ab3c7/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:94bb451c664415f02f07eef4ece976a2c65dcbab9c2f1705b7031a3a75349d8c", size = 312788, upload-time = "2025-04-17T22:37:03.578Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b8/10fbec38f82c5d163ca1750bfff4ede69713badf236a016781cf1f10a0f0/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d1a686d0b0949182b8faddea596f3fc11f44768d1f74d4cad70213b2e139d821", size = 325914, upload-time = "2025-04-17T22:37:05.213Z" }, - { url = "https://files.pythonhosted.org/packages/62/ca/2bf4f3a1bd40cdedd301e6ecfdbb291080d5afc5f9ce350c0739f773d6b9/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea8e59105d802c5a38bdbe7362822c522230b3faba2aa35c0fa1765239b7dd70", size = 305283, upload-time = "2025-04-17T22:37:06.985Z" }, - { url = "https://files.pythonhosted.org/packages/09/64/20cc13ccf94abc2a1f482f74ad210703dc78a590d0b805af1c9aa67f76f9/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:abc4e880a9b920bc5020bf6a431a6bb40589d9bca3975c980495f63632e8382f", size = 319264, upload-time = "2025-04-17T22:37:08.618Z" }, - { url = "https://files.pythonhosted.org/packages/20/ff/86c6a2bbe98cfc231519f5e6d712a0898488ceac804a917ce014f32e68f6/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a79713adfe28830f27a3c62f6b5406c37376c892b05ae070906f07ae4487046", size = 326482, upload-time = "2025-04-17T22:37:10.196Z" }, - { url = "https://files.pythonhosted.org/packages/2f/da/8e381f66367d79adca245d1d71527aac774e30e291d41ef161ce2d80c38e/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a0318c2068e217a8f5e3b85e35899f5a19e97141a45bb925bb357cfe1daf770", size = 318248, upload-time = "2025-04-17T22:37:12.284Z" }, - { url = "https://files.pythonhosted.org/packages/39/24/1a1976563fb476ab6f0fa9fefaac7616a4361dbe0461324f9fd7bf425dbe/frozenlist-1.6.0-cp312-cp312-win32.whl", hash = "sha256:853ac025092a24bb3bf09ae87f9127de9fe6e0c345614ac92536577cf956dfcc", size = 115161, upload-time = "2025-04-17T22:37:13.902Z" }, - { url = "https://files.pythonhosted.org/packages/80/2e/fb4ed62a65f8cd66044706b1013f0010930d8cbb0729a2219561ea075434/frozenlist-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bdfe2d7e6c9281c6e55523acd6c2bf77963cb422fdc7d142fb0cb6621b66878", size = 120548, upload-time = "2025-04-17T22:37:15.326Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e5/04c7090c514d96ca00887932417f04343ab94904a56ab7f57861bf63652d/frozenlist-1.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1d7fb014fe0fbfee3efd6a94fc635aeaa68e5e1720fe9e57357f2e2c6e1a647e", size = 158182, upload-time = "2025-04-17T22:37:16.837Z" }, - { url = "https://files.pythonhosted.org/packages/e9/8f/60d0555c61eec855783a6356268314d204137f5e0c53b59ae2fc28938c99/frozenlist-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01bcaa305a0fdad12745502bfd16a1c75b14558dabae226852f9159364573117", size = 122838, upload-time = "2025-04-17T22:37:18.352Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a7/d0ec890e3665b4b3b7c05dc80e477ed8dc2e2e77719368e78e2cd9fec9c8/frozenlist-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b314faa3051a6d45da196a2c495e922f987dc848e967d8cfeaee8a0328b1cd4", size = 120980, upload-time = "2025-04-17T22:37:19.857Z" }, - { url = "https://files.pythonhosted.org/packages/cc/19/9b355a5e7a8eba903a008579964192c3e427444752f20b2144b10bb336df/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da62fecac21a3ee10463d153549d8db87549a5e77eefb8c91ac84bb42bb1e4e3", size = 305463, upload-time = "2025-04-17T22:37:21.328Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8d/5b4c758c2550131d66935ef2fa700ada2461c08866aef4229ae1554b93ca/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1eb89bf3454e2132e046f9599fbcf0a4483ed43b40f545551a39316d0201cd1", size = 297985, upload-time = "2025-04-17T22:37:23.55Z" }, - { url = "https://files.pythonhosted.org/packages/48/2c/537ec09e032b5865715726b2d1d9813e6589b571d34d01550c7aeaad7e53/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18689b40cb3936acd971f663ccb8e2589c45db5e2c5f07e0ec6207664029a9c", size = 311188, upload-time = "2025-04-17T22:37:25.221Z" }, - { url = "https://files.pythonhosted.org/packages/31/2f/1aa74b33f74d54817055de9a4961eff798f066cdc6f67591905d4fc82a84/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e67ddb0749ed066b1a03fba812e2dcae791dd50e5da03be50b6a14d0c1a9ee45", size = 311874, upload-time = "2025-04-17T22:37:26.791Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f0/cfec18838f13ebf4b37cfebc8649db5ea71a1b25dacd691444a10729776c/frozenlist-1.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc5e64626e6682638d6e44398c9baf1d6ce6bc236d40b4b57255c9d3f9761f1f", size = 291897, upload-time = "2025-04-17T22:37:28.958Z" }, - { url = "https://files.pythonhosted.org/packages/ea/a5/deb39325cbbea6cd0a46db8ccd76150ae2fcbe60d63243d9df4a0b8c3205/frozenlist-1.6.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:437cfd39564744ae32ad5929e55b18ebd88817f9180e4cc05e7d53b75f79ce85", size = 305799, upload-time = "2025-04-17T22:37:30.889Z" }, - { url = "https://files.pythonhosted.org/packages/78/22/6ddec55c5243a59f605e4280f10cee8c95a449f81e40117163383829c241/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62dd7df78e74d924952e2feb7357d826af8d2f307557a779d14ddf94d7311be8", size = 302804, upload-time = "2025-04-17T22:37:32.489Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b7/d9ca9bab87f28855063c4d202936800219e39db9e46f9fb004d521152623/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a66781d7e4cddcbbcfd64de3d41a61d6bdde370fc2e38623f30b2bd539e84a9f", size = 316404, upload-time = "2025-04-17T22:37:34.59Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3a/1255305db7874d0b9eddb4fe4a27469e1fb63720f1fc6d325a5118492d18/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:482fe06e9a3fffbcd41950f9d890034b4a54395c60b5e61fae875d37a699813f", size = 295572, upload-time = "2025-04-17T22:37:36.337Z" }, - { url = "https://files.pythonhosted.org/packages/2a/f2/8d38eeee39a0e3a91b75867cc102159ecccf441deb6ddf67be96d3410b84/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e4f9373c500dfc02feea39f7a56e4f543e670212102cc2eeb51d3a99c7ffbde6", size = 307601, upload-time = "2025-04-17T22:37:37.923Z" }, - { url = "https://files.pythonhosted.org/packages/38/04/80ec8e6b92f61ef085422d7b196822820404f940950dde5b2e367bede8bc/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e69bb81de06827147b7bfbaeb284d85219fa92d9f097e32cc73675f279d70188", size = 314232, upload-time = "2025-04-17T22:37:39.669Z" }, - { url = "https://files.pythonhosted.org/packages/3a/58/93b41fb23e75f38f453ae92a2f987274c64637c450285577bd81c599b715/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7613d9977d2ab4a9141dde4a149f4357e4065949674c5649f920fec86ecb393e", size = 308187, upload-time = "2025-04-17T22:37:41.662Z" }, - { url = "https://files.pythonhosted.org/packages/6a/a2/e64df5c5aa36ab3dee5a40d254f3e471bb0603c225f81664267281c46a2d/frozenlist-1.6.0-cp313-cp313-win32.whl", hash = "sha256:4def87ef6d90429f777c9d9de3961679abf938cb6b7b63d4a7eb8a268babfce4", size = 114772, upload-time = "2025-04-17T22:37:43.132Z" }, - { url = "https://files.pythonhosted.org/packages/a0/77/fead27441e749b2d574bb73d693530d59d520d4b9e9679b8e3cb779d37f2/frozenlist-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:37a8a52c3dfff01515e9bbbee0e6063181362f9de3db2ccf9bc96189b557cbfd", size = 119847, upload-time = "2025-04-17T22:37:45.118Z" }, - { url = "https://files.pythonhosted.org/packages/df/bd/cc6d934991c1e5d9cafda83dfdc52f987c7b28343686aef2e58a9cf89f20/frozenlist-1.6.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:46138f5a0773d064ff663d273b309b696293d7a7c00a0994c5c13a5078134b64", size = 174937, upload-time = "2025-04-17T22:37:46.635Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a2/daf945f335abdbfdd5993e9dc348ef4507436936ab3c26d7cfe72f4843bf/frozenlist-1.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f88bc0a2b9c2a835cb888b32246c27cdab5740059fb3688852bf91e915399b91", size = 136029, upload-time = "2025-04-17T22:37:48.192Z" }, - { url = "https://files.pythonhosted.org/packages/51/65/4c3145f237a31247c3429e1c94c384d053f69b52110a0d04bfc8afc55fb2/frozenlist-1.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:777704c1d7655b802c7850255639672e90e81ad6fa42b99ce5ed3fbf45e338dd", size = 134831, upload-time = "2025-04-17T22:37:50.485Z" }, - { url = "https://files.pythonhosted.org/packages/77/38/03d316507d8dea84dfb99bdd515ea245628af964b2bf57759e3c9205cc5e/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ef8d41764c7de0dcdaf64f733a27352248493a85a80661f3c678acd27e31f2", size = 392981, upload-time = "2025-04-17T22:37:52.558Z" }, - { url = "https://files.pythonhosted.org/packages/37/02/46285ef9828f318ba400a51d5bb616ded38db8466836a9cfa39f3903260b/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:da5cb36623f2b846fb25009d9d9215322318ff1c63403075f812b3b2876c8506", size = 371999, upload-time = "2025-04-17T22:37:54.092Z" }, - { url = "https://files.pythonhosted.org/packages/0d/64/1212fea37a112c3c5c05bfb5f0a81af4836ce349e69be75af93f99644da9/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbb56587a16cf0fb8acd19e90ff9924979ac1431baea8681712716a8337577b0", size = 392200, upload-time = "2025-04-17T22:37:55.951Z" }, - { url = "https://files.pythonhosted.org/packages/81/ce/9a6ea1763e3366e44a5208f76bf37c76c5da570772375e4d0be85180e588/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6154c3ba59cda3f954c6333025369e42c3acd0c6e8b6ce31eb5c5b8116c07e0", size = 390134, upload-time = "2025-04-17T22:37:57.633Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/939738b0b495b2c6d0c39ba51563e453232813042a8d908b8f9544296c29/frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e8246877afa3f1ae5c979fe85f567d220f86a50dc6c493b9b7d8191181ae01e", size = 365208, upload-time = "2025-04-17T22:37:59.742Z" }, - { url = "https://files.pythonhosted.org/packages/b4/8b/939e62e93c63409949c25220d1ba8e88e3960f8ef6a8d9ede8f94b459d27/frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0f6cce16306d2e117cf9db71ab3a9e8878a28176aeaf0dbe35248d97b28d0c", size = 385548, upload-time = "2025-04-17T22:38:01.416Z" }, - { url = "https://files.pythonhosted.org/packages/62/38/22d2873c90102e06a7c5a3a5b82ca47e393c6079413e8a75c72bff067fa8/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1b8e8cd8032ba266f91136d7105706ad57770f3522eac4a111d77ac126a25a9b", size = 391123, upload-time = "2025-04-17T22:38:03.049Z" }, - { url = "https://files.pythonhosted.org/packages/44/78/63aaaf533ee0701549500f6d819be092c6065cb5c577edb70c09df74d5d0/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e2ada1d8515d3ea5378c018a5f6d14b4994d4036591a52ceaf1a1549dec8e1ad", size = 394199, upload-time = "2025-04-17T22:38:04.776Z" }, - { url = "https://files.pythonhosted.org/packages/54/45/71a6b48981d429e8fbcc08454dc99c4c2639865a646d549812883e9c9dd3/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:cdb2c7f071e4026c19a3e32b93a09e59b12000751fc9b0b7758da899e657d215", size = 373854, upload-time = "2025-04-17T22:38:06.576Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f3/dbf2a5e11736ea81a66e37288bf9f881143a7822b288a992579ba1b4204d/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:03572933a1969a6d6ab509d509e5af82ef80d4a5d4e1e9f2e1cdd22c77a3f4d2", size = 395412, upload-time = "2025-04-17T22:38:08.197Z" }, - { url = "https://files.pythonhosted.org/packages/b3/f1/c63166806b331f05104d8ea385c4acd511598568b1f3e4e8297ca54f2676/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:77effc978947548b676c54bbd6a08992759ea6f410d4987d69feea9cd0919911", size = 394936, upload-time = "2025-04-17T22:38:10.056Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ea/4f3e69e179a430473eaa1a75ff986526571215fefc6b9281cdc1f09a4eb8/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a2bda8be77660ad4089caf2223fdbd6db1858462c4b85b67fbfa22102021e497", size = 391459, upload-time = "2025-04-17T22:38:11.826Z" }, - { url = "https://files.pythonhosted.org/packages/d3/c3/0fc2c97dea550df9afd072a37c1e95421652e3206bbeaa02378b24c2b480/frozenlist-1.6.0-cp313-cp313t-win32.whl", hash = "sha256:a4d96dc5bcdbd834ec6b0f91027817214216b5b30316494d2b1aebffb87c534f", size = 128797, upload-time = "2025-04-17T22:38:14.013Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f5/79c9320c5656b1965634fe4be9c82b12a3305bdbc58ad9cb941131107b20/frozenlist-1.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e18036cb4caa17ea151fd5f3d70be9d354c99eb8cf817a3ccde8a7873b074348", size = 134709, upload-time = "2025-04-17T22:38:15.551Z" }, - { url = "https://files.pythonhosted.org/packages/71/3e/b04a0adda73bd52b390d730071c0d577073d3d26740ee1bad25c3ad0f37b/frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191", size = 12404, upload-time = "2025-04-17T22:38:51.668Z" }, +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [[package]] @@ -704,83 +592,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" }, ] -[[package]] -name = "multidict" -version = "6.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/2c/e367dfb4c6538614a0c9453e510d75d66099edf1c4e69da1b5ce691a1931/multidict-6.4.3.tar.gz", hash = "sha256:3ada0b058c9f213c5f95ba301f922d402ac234f1111a7d8fd70f1b99f3c281ec", size = 89372, upload-time = "2025-04-10T22:20:17.956Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e0/53cf7f27eda48fffa53cfd4502329ed29e00efb9e4ce41362cbf8aa54310/multidict-6.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f6f19170197cc29baccd33ccc5b5d6a331058796485857cf34f7635aa25fb0cd", size = 65259, upload-time = "2025-04-10T22:17:59.632Z" }, - { url = "https://files.pythonhosted.org/packages/44/79/1dcd93ce7070cf01c2ee29f781c42b33c64fce20033808f1cc9ec8413d6e/multidict-6.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2882bf27037eb687e49591690e5d491e677272964f9ec7bc2abbe09108bdfb8", size = 38451, upload-time = "2025-04-10T22:18:01.202Z" }, - { url = "https://files.pythonhosted.org/packages/f4/35/2292cf29ab5f0d0b3613fad1b75692148959d3834d806be1885ceb49a8ff/multidict-6.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbf226ac85f7d6b6b9ba77db4ec0704fde88463dc17717aec78ec3c8546c70ad", size = 37706, upload-time = "2025-04-10T22:18:02.276Z" }, - { url = "https://files.pythonhosted.org/packages/f6/d1/6b157110b2b187b5a608b37714acb15ee89ec773e3800315b0107ea648cd/multidict-6.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e329114f82ad4b9dd291bef614ea8971ec119ecd0f54795109976de75c9a852", size = 226669, upload-time = "2025-04-10T22:18:03.436Z" }, - { url = "https://files.pythonhosted.org/packages/40/7f/61a476450651f177c5570e04bd55947f693077ba7804fe9717ee9ae8de04/multidict-6.4.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1f4e0334d7a555c63f5c8952c57ab6f1c7b4f8c7f3442df689fc9f03df315c08", size = 223182, upload-time = "2025-04-10T22:18:04.922Z" }, - { url = "https://files.pythonhosted.org/packages/51/7b/eaf7502ac4824cdd8edcf5723e2e99f390c879866aec7b0c420267b53749/multidict-6.4.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:740915eb776617b57142ce0bb13b7596933496e2f798d3d15a20614adf30d229", size = 235025, upload-time = "2025-04-10T22:18:06.274Z" }, - { url = "https://files.pythonhosted.org/packages/3b/f6/facdbbd73c96b67a93652774edd5778ab1167854fa08ea35ad004b1b70ad/multidict-6.4.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255dac25134d2b141c944b59a0d2f7211ca12a6d4779f7586a98b4b03ea80508", size = 231481, upload-time = "2025-04-10T22:18:07.742Z" }, - { url = "https://files.pythonhosted.org/packages/70/57/c008e861b3052405eebf921fd56a748322d8c44dcfcab164fffbccbdcdc4/multidict-6.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4e8535bd4d741039b5aad4285ecd9b902ef9e224711f0b6afda6e38d7ac02c7", size = 223492, upload-time = "2025-04-10T22:18:09.095Z" }, - { url = "https://files.pythonhosted.org/packages/30/4d/7d8440d3a12a6ae5d6b202d6e7f2ac6ab026e04e99aaf1b73f18e6bc34bc/multidict-6.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c433a33be000dd968f5750722eaa0991037be0be4a9d453eba121774985bc8", size = 217279, upload-time = "2025-04-10T22:18:10.474Z" }, - { url = "https://files.pythonhosted.org/packages/7f/e7/bca0df4dd057597b94138d2d8af04eb3c27396a425b1b0a52e082f9be621/multidict-6.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4eb33b0bdc50acd538f45041f5f19945a1f32b909b76d7b117c0c25d8063df56", size = 228733, upload-time = "2025-04-10T22:18:11.793Z" }, - { url = "https://files.pythonhosted.org/packages/88/f5/383827c3f1c38d7c92dbad00a8a041760228573b1c542fbf245c37bbca8a/multidict-6.4.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:75482f43465edefd8a5d72724887ccdcd0c83778ded8f0cb1e0594bf71736cc0", size = 218089, upload-time = "2025-04-10T22:18:13.153Z" }, - { url = "https://files.pythonhosted.org/packages/36/8a/a5174e8a7d8b94b4c8f9c1e2cf5d07451f41368ffe94d05fc957215b8e72/multidict-6.4.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce5b3082e86aee80b3925ab4928198450d8e5b6466e11501fe03ad2191c6d777", size = 225257, upload-time = "2025-04-10T22:18:14.654Z" }, - { url = "https://files.pythonhosted.org/packages/8c/76/1d4b7218f0fd00b8e5c90b88df2e45f8af127f652f4e41add947fa54c1c4/multidict-6.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e413152e3212c4d39f82cf83c6f91be44bec9ddea950ce17af87fbf4e32ca6b2", size = 234728, upload-time = "2025-04-10T22:18:16.236Z" }, - { url = "https://files.pythonhosted.org/packages/64/44/18372a4f6273fc7ca25630d7bf9ae288cde64f29593a078bff450c7170b6/multidict-6.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8aac2eeff69b71f229a405c0a4b61b54bade8e10163bc7b44fcd257949620618", size = 230087, upload-time = "2025-04-10T22:18:17.979Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ae/28728c314a698d8a6d9491fcacc897077348ec28dd85884d09e64df8a855/multidict-6.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab583ac203af1d09034be41458feeab7863c0635c650a16f15771e1386abf2d7", size = 223137, upload-time = "2025-04-10T22:18:19.362Z" }, - { url = "https://files.pythonhosted.org/packages/22/50/785bb2b3fe16051bc91c70a06a919f26312da45c34db97fc87441d61e343/multidict-6.4.3-cp311-cp311-win32.whl", hash = "sha256:1b2019317726f41e81154df636a897de1bfe9228c3724a433894e44cd2512378", size = 34959, upload-time = "2025-04-10T22:18:20.728Z" }, - { url = "https://files.pythonhosted.org/packages/2f/63/2a22e099ae2f4d92897618c00c73a09a08a2a9aa14b12736965bf8d59fd3/multidict-6.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:43173924fa93c7486402217fab99b60baf78d33806af299c56133a3755f69589", size = 38541, upload-time = "2025-04-10T22:18:22.001Z" }, - { url = "https://files.pythonhosted.org/packages/fc/bb/3abdaf8fe40e9226ce8a2ba5ecf332461f7beec478a455d6587159f1bf92/multidict-6.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f1c2f58f08b36f8475f3ec6f5aeb95270921d418bf18f90dffd6be5c7b0e676", size = 64019, upload-time = "2025-04-10T22:18:23.174Z" }, - { url = "https://files.pythonhosted.org/packages/7e/b5/1b2e8de8217d2e89db156625aa0fe4a6faad98972bfe07a7b8c10ef5dd6b/multidict-6.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:26ae9ad364fc61b936fb7bf4c9d8bd53f3a5b4417142cd0be5c509d6f767e2f1", size = 37925, upload-time = "2025-04-10T22:18:24.834Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e2/3ca91c112644a395c8eae017144c907d173ea910c913ff8b62549dcf0bbf/multidict-6.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:659318c6c8a85f6ecfc06b4e57529e5a78dfdd697260cc81f683492ad7e9435a", size = 37008, upload-time = "2025-04-10T22:18:26.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/23/79bc78146c7ac8d1ac766b2770ca2e07c2816058b8a3d5da6caed8148637/multidict-6.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1eb72c741fd24d5a28242ce72bb61bc91f8451877131fa3fe930edb195f7054", size = 224374, upload-time = "2025-04-10T22:18:27.714Z" }, - { url = "https://files.pythonhosted.org/packages/86/35/77950ed9ebd09136003a85c1926ba42001ca5be14feb49710e4334ee199b/multidict-6.4.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3cd06d88cb7398252284ee75c8db8e680aa0d321451132d0dba12bc995f0adcc", size = 230869, upload-time = "2025-04-10T22:18:29.162Z" }, - { url = "https://files.pythonhosted.org/packages/49/97/2a33c6e7d90bc116c636c14b2abab93d6521c0c052d24bfcc231cbf7f0e7/multidict-6.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4543d8dc6470a82fde92b035a92529317191ce993533c3c0c68f56811164ed07", size = 231949, upload-time = "2025-04-10T22:18:30.679Z" }, - { url = "https://files.pythonhosted.org/packages/56/ce/e9b5d9fcf854f61d6686ada7ff64893a7a5523b2a07da6f1265eaaea5151/multidict-6.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30a3ebdc068c27e9d6081fca0e2c33fdf132ecea703a72ea216b81a66860adde", size = 231032, upload-time = "2025-04-10T22:18:32.146Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ac/7ced59dcdfeddd03e601edb05adff0c66d81ed4a5160c443e44f2379eef0/multidict-6.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b038f10e23f277153f86f95c777ba1958bcd5993194fda26a1d06fae98b2f00c", size = 223517, upload-time = "2025-04-10T22:18:33.538Z" }, - { url = "https://files.pythonhosted.org/packages/db/e6/325ed9055ae4e085315193a1b58bdb4d7fc38ffcc1f4975cfca97d015e17/multidict-6.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c605a2b2dc14282b580454b9b5d14ebe0668381a3a26d0ac39daa0ca115eb2ae", size = 216291, upload-time = "2025-04-10T22:18:34.962Z" }, - { url = "https://files.pythonhosted.org/packages/fa/84/eeee6d477dd9dcb7691c3bb9d08df56017f5dd15c730bcc9383dcf201cf4/multidict-6.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8bd2b875f4ca2bb527fe23e318ddd509b7df163407b0fb717df229041c6df5d3", size = 228982, upload-time = "2025-04-10T22:18:36.443Z" }, - { url = "https://files.pythonhosted.org/packages/82/94/4d1f3e74e7acf8b0c85db350e012dcc61701cd6668bc2440bb1ecb423c90/multidict-6.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c2e98c840c9c8e65c0e04b40c6c5066c8632678cd50c8721fdbcd2e09f21a507", size = 226823, upload-time = "2025-04-10T22:18:37.924Z" }, - { url = "https://files.pythonhosted.org/packages/09/f0/1e54b95bda7cd01080e5732f9abb7b76ab5cc795b66605877caeb2197476/multidict-6.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66eb80dd0ab36dbd559635e62fba3083a48a252633164857a1d1684f14326427", size = 222714, upload-time = "2025-04-10T22:18:39.807Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a2/f6cbca875195bd65a3e53b37ab46486f3cc125bdeab20eefe5042afa31fb/multidict-6.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c23831bdee0a2a3cf21be057b5e5326292f60472fb6c6f86392bbf0de70ba731", size = 233739, upload-time = "2025-04-10T22:18:41.341Z" }, - { url = "https://files.pythonhosted.org/packages/79/68/9891f4d2b8569554723ddd6154375295f789dc65809826c6fb96a06314fd/multidict-6.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1535cec6443bfd80d028052e9d17ba6ff8a5a3534c51d285ba56c18af97e9713", size = 230809, upload-time = "2025-04-10T22:18:42.817Z" }, - { url = "https://files.pythonhosted.org/packages/e6/72/a7be29ba1e87e4fc5ceb44dabc7940b8005fd2436a332a23547709315f70/multidict-6.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b73e7227681f85d19dec46e5b881827cd354aabe46049e1a61d2f9aaa4e285a", size = 226934, upload-time = "2025-04-10T22:18:44.311Z" }, - { url = "https://files.pythonhosted.org/packages/12/c1/259386a9ad6840ff7afc686da96808b503d152ac4feb3a96c651dc4f5abf/multidict-6.4.3-cp312-cp312-win32.whl", hash = "sha256:8eac0c49df91b88bf91f818e0a24c1c46f3622978e2c27035bfdca98e0e18124", size = 35242, upload-time = "2025-04-10T22:18:46.193Z" }, - { url = "https://files.pythonhosted.org/packages/06/24/c8fdff4f924d37225dc0c56a28b1dca10728fc2233065fafeb27b4b125be/multidict-6.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:11990b5c757d956cd1db7cb140be50a63216af32cd6506329c2c59d732d802db", size = 38635, upload-time = "2025-04-10T22:18:47.498Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4b/86fd786d03915c6f49998cf10cd5fe6b6ac9e9a071cb40885d2e080fb90d/multidict-6.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a76534263d03ae0cfa721fea40fd2b5b9d17a6f85e98025931d41dc49504474", size = 63831, upload-time = "2025-04-10T22:18:48.748Z" }, - { url = "https://files.pythonhosted.org/packages/45/05/9b51fdf7aef2563340a93be0a663acba2c428c4daeaf3960d92d53a4a930/multidict-6.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:805031c2f599eee62ac579843555ed1ce389ae00c7e9f74c2a1b45e0564a88dd", size = 37888, upload-time = "2025-04-10T22:18:50.021Z" }, - { url = "https://files.pythonhosted.org/packages/0b/43/53fc25394386c911822419b522181227ca450cf57fea76e6188772a1bd91/multidict-6.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c56c179839d5dcf51d565132185409d1d5dd8e614ba501eb79023a6cab25576b", size = 36852, upload-time = "2025-04-10T22:18:51.246Z" }, - { url = "https://files.pythonhosted.org/packages/8a/68/7b99c751e822467c94a235b810a2fd4047d4ecb91caef6b5c60116991c4b/multidict-6.4.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c64f4ddb3886dd8ab71b68a7431ad4aa01a8fa5be5b11543b29674f29ca0ba3", size = 223644, upload-time = "2025-04-10T22:18:52.965Z" }, - { url = "https://files.pythonhosted.org/packages/80/1b/d458d791e4dd0f7e92596667784fbf99e5c8ba040affe1ca04f06b93ae92/multidict-6.4.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3002a856367c0b41cad6784f5b8d3ab008eda194ed7864aaa58f65312e2abcac", size = 230446, upload-time = "2025-04-10T22:18:54.509Z" }, - { url = "https://files.pythonhosted.org/packages/e2/46/9793378d988905491a7806d8987862dc5a0bae8a622dd896c4008c7b226b/multidict-6.4.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d75e621e7d887d539d6e1d789f0c64271c250276c333480a9e1de089611f790", size = 231070, upload-time = "2025-04-10T22:18:56.019Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b8/b127d3e1f8dd2a5bf286b47b24567ae6363017292dc6dec44656e6246498/multidict-6.4.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:995015cf4a3c0d72cbf453b10a999b92c5629eaf3a0c3e1efb4b5c1f602253bb", size = 229956, upload-time = "2025-04-10T22:18:59.146Z" }, - { url = "https://files.pythonhosted.org/packages/0c/93/f70a4c35b103fcfe1443059a2bb7f66e5c35f2aea7804105ff214f566009/multidict-6.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b0fabae7939d09d7d16a711468c385272fa1b9b7fb0d37e51143585d8e72e0", size = 222599, upload-time = "2025-04-10T22:19:00.657Z" }, - { url = "https://files.pythonhosted.org/packages/63/8c/e28e0eb2fe34921d6aa32bfc4ac75b09570b4d6818cc95d25499fe08dc1d/multidict-6.4.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61ed4d82f8a1e67eb9eb04f8587970d78fe7cddb4e4d6230b77eda23d27938f9", size = 216136, upload-time = "2025-04-10T22:19:02.244Z" }, - { url = "https://files.pythonhosted.org/packages/72/f5/fbc81f866585b05f89f99d108be5d6ad170e3b6c4d0723d1a2f6ba5fa918/multidict-6.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:062428944a8dc69df9fdc5d5fc6279421e5f9c75a9ee3f586f274ba7b05ab3c8", size = 228139, upload-time = "2025-04-10T22:19:04.151Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ba/7d196bad6b85af2307d81f6979c36ed9665f49626f66d883d6c64d156f78/multidict-6.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b90e27b4674e6c405ad6c64e515a505c6d113b832df52fdacb6b1ffd1fa9a1d1", size = 226251, upload-time = "2025-04-10T22:19:06.117Z" }, - { url = "https://files.pythonhosted.org/packages/cc/e2/fae46a370dce79d08b672422a33df721ec8b80105e0ea8d87215ff6b090d/multidict-6.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7d50d4abf6729921e9613d98344b74241572b751c6b37feed75fb0c37bd5a817", size = 221868, upload-time = "2025-04-10T22:19:07.981Z" }, - { url = "https://files.pythonhosted.org/packages/26/20/bbc9a3dec19d5492f54a167f08546656e7aef75d181d3d82541463450e88/multidict-6.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:43fe10524fb0a0514be3954be53258e61d87341008ce4914f8e8b92bee6f875d", size = 233106, upload-time = "2025-04-10T22:19:09.5Z" }, - { url = "https://files.pythonhosted.org/packages/ee/8d/f30ae8f5ff7a2461177f4d8eb0d8f69f27fb6cfe276b54ec4fd5a282d918/multidict-6.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:236966ca6c472ea4e2d3f02f6673ebfd36ba3f23159c323f5a496869bc8e47c9", size = 230163, upload-time = "2025-04-10T22:19:11Z" }, - { url = "https://files.pythonhosted.org/packages/15/e9/2833f3c218d3c2179f3093f766940ded6b81a49d2e2f9c46ab240d23dfec/multidict-6.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:422a5ec315018e606473ba1f5431e064cf8b2a7468019233dcf8082fabad64c8", size = 225906, upload-time = "2025-04-10T22:19:12.875Z" }, - { url = "https://files.pythonhosted.org/packages/f1/31/6edab296ac369fd286b845fa5dd4c409e63bc4655ed8c9510fcb477e9ae9/multidict-6.4.3-cp313-cp313-win32.whl", hash = "sha256:f901a5aace8e8c25d78960dcc24c870c8d356660d3b49b93a78bf38eb682aac3", size = 35238, upload-time = "2025-04-10T22:19:14.41Z" }, - { url = "https://files.pythonhosted.org/packages/23/57/2c0167a1bffa30d9a1383c3dab99d8caae985defc8636934b5668830d2ef/multidict-6.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:1c152c49e42277bc9a2f7b78bd5fa10b13e88d1b0328221e7aef89d5c60a99a5", size = 38799, upload-time = "2025-04-10T22:19:15.869Z" }, - { url = "https://files.pythonhosted.org/packages/c9/13/2ead63b9ab0d2b3080819268acb297bd66e238070aa8d42af12b08cbee1c/multidict-6.4.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:be8751869e28b9c0d368d94f5afcb4234db66fe8496144547b4b6d6a0645cfc6", size = 68642, upload-time = "2025-04-10T22:19:17.527Z" }, - { url = "https://files.pythonhosted.org/packages/85/45/f1a751e1eede30c23951e2ae274ce8fad738e8a3d5714be73e0a41b27b16/multidict-6.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d4b31f8a68dccbcd2c0ea04f0e014f1defc6b78f0eb8b35f2265e8716a6df0c", size = 40028, upload-time = "2025-04-10T22:19:19.465Z" }, - { url = "https://files.pythonhosted.org/packages/a7/29/fcc53e886a2cc5595cc4560df333cb9630257bda65003a7eb4e4e0d8f9c1/multidict-6.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:032efeab3049e37eef2ff91271884303becc9e54d740b492a93b7e7266e23756", size = 39424, upload-time = "2025-04-10T22:19:20.762Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f0/056c81119d8b88703971f937b371795cab1407cd3c751482de5bfe1a04a9/multidict-6.4.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e78006af1a7c8a8007e4f56629d7252668344442f66982368ac06522445e375", size = 226178, upload-time = "2025-04-10T22:19:22.17Z" }, - { url = "https://files.pythonhosted.org/packages/a3/79/3b7e5fea0aa80583d3a69c9d98b7913dfd4fbc341fb10bb2fb48d35a9c21/multidict-6.4.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:daeac9dd30cda8703c417e4fddccd7c4dc0c73421a0b54a7da2713be125846be", size = 222617, upload-time = "2025-04-10T22:19:23.773Z" }, - { url = "https://files.pythonhosted.org/packages/06/db/3ed012b163e376fc461e1d6a67de69b408339bc31dc83d39ae9ec3bf9578/multidict-6.4.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f6f90700881438953eae443a9c6f8a509808bc3b185246992c4233ccee37fea", size = 227919, upload-time = "2025-04-10T22:19:25.35Z" }, - { url = "https://files.pythonhosted.org/packages/b1/db/0433c104bca380989bc04d3b841fc83e95ce0c89f680e9ea4251118b52b6/multidict-6.4.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f84627997008390dd15762128dcf73c3365f4ec0106739cde6c20a07ed198ec8", size = 226097, upload-time = "2025-04-10T22:19:27.183Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/910db2618175724dd254b7ae635b6cd8d2947a8b76b0376de7b96d814dab/multidict-6.4.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3307b48cd156153b117c0ea54890a3bdbf858a5b296ddd40dc3852e5f16e9b02", size = 220706, upload-time = "2025-04-10T22:19:28.882Z" }, - { url = "https://files.pythonhosted.org/packages/d1/af/aa176c6f5f1d901aac957d5258d5e22897fe13948d1e69063ae3d5d0ca01/multidict-6.4.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead46b0fa1dcf5af503a46e9f1c2e80b5d95c6011526352fa5f42ea201526124", size = 211728, upload-time = "2025-04-10T22:19:30.481Z" }, - { url = "https://files.pythonhosted.org/packages/e7/42/d51cc5fc1527c3717d7f85137d6c79bb7a93cd214c26f1fc57523774dbb5/multidict-6.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1748cb2743bedc339d63eb1bca314061568793acd603a6e37b09a326334c9f44", size = 226276, upload-time = "2025-04-10T22:19:32.454Z" }, - { url = "https://files.pythonhosted.org/packages/28/6b/d836dea45e0b8432343ba4acf9a8ecaa245da4c0960fb7ab45088a5e568a/multidict-6.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:acc9fa606f76fc111b4569348cc23a771cb52c61516dcc6bcef46d612edb483b", size = 212069, upload-time = "2025-04-10T22:19:34.17Z" }, - { url = "https://files.pythonhosted.org/packages/55/34/0ee1a7adb3560e18ee9289c6e5f7db54edc312b13e5c8263e88ea373d12c/multidict-6.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:31469d5832b5885adeb70982e531ce86f8c992334edd2f2254a10fa3182ac504", size = 217858, upload-time = "2025-04-10T22:19:35.879Z" }, - { url = "https://files.pythonhosted.org/packages/04/08/586d652c2f5acefe0cf4e658eedb4d71d4ba6dfd4f189bd81b400fc1bc6b/multidict-6.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ba46b51b6e51b4ef7bfb84b82f5db0dc5e300fb222a8a13b8cd4111898a869cf", size = 226988, upload-time = "2025-04-10T22:19:37.434Z" }, - { url = "https://files.pythonhosted.org/packages/82/e3/cc59c7e2bc49d7f906fb4ffb6d9c3a3cf21b9f2dd9c96d05bef89c2b1fd1/multidict-6.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:389cfefb599edf3fcfd5f64c0410da686f90f5f5e2c4d84e14f6797a5a337af4", size = 220435, upload-time = "2025-04-10T22:19:39.005Z" }, - { url = "https://files.pythonhosted.org/packages/e0/32/5c3a556118aca9981d883f38c4b1bfae646f3627157f70f4068e5a648955/multidict-6.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:64bc2bbc5fba7b9db5c2c8d750824f41c6994e3882e6d73c903c2afa78d091e4", size = 221494, upload-time = "2025-04-10T22:19:41.447Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3b/1599631f59024b75c4d6e3069f4502409970a336647502aaf6b62fb7ac98/multidict-6.4.3-cp313-cp313t-win32.whl", hash = "sha256:0ecdc12ea44bab2807d6b4a7e5eef25109ab1c82a8240d86d3c1fc9f3b72efd5", size = 41775, upload-time = "2025-04-10T22:19:43.707Z" }, - { url = "https://files.pythonhosted.org/packages/e8/4e/09301668d675d02ca8e8e1a3e6be046619e30403f5ada2ed5b080ae28d02/multidict-6.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7146a8742ea71b5d7d955bffcef58a9e6e04efba704b52a460134fefd10a8208", size = 45946, upload-time = "2025-04-10T22:19:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/96/10/7d526c8974f017f1e7ca584c71ee62a638e9334d8d33f27d7cdfc9ae79e4/multidict-6.4.3-py3-none-any.whl", hash = "sha256:59fe01ee8e2a1e8ceb3f6dbb216b09c8d9f4ef1c22c4fc825d045a147fa2ebc9", size = 10400, upload-time = "2025-04-10T22:20:16.445Z" }, -] - [[package]] name = "mypy" version = "1.15.0" @@ -930,79 +741,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, ] -[[package]] -name = "propcache" -version = "0.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/07/c8/fdc6686a986feae3541ea23dcaa661bd93972d3940460646c6bb96e21c40/propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf", size = 43651, upload-time = "2025-03-26T03:06:12.05Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/0f/5a5319ee83bd651f75311fcb0c492c21322a7fc8f788e4eef23f44243427/propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5", size = 80243, upload-time = "2025-03-26T03:04:01.912Z" }, - { url = "https://files.pythonhosted.org/packages/ce/84/3db5537e0879942783e2256616ff15d870a11d7ac26541336fe1b673c818/propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371", size = 46503, upload-time = "2025-03-26T03:04:03.704Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c8/b649ed972433c3f0d827d7f0cf9ea47162f4ef8f4fe98c5f3641a0bc63ff/propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da", size = 45934, upload-time = "2025-03-26T03:04:05.257Z" }, - { url = "https://files.pythonhosted.org/packages/59/f9/4c0a5cf6974c2c43b1a6810c40d889769cc8f84cea676cbe1e62766a45f8/propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744", size = 233633, upload-time = "2025-03-26T03:04:07.044Z" }, - { url = "https://files.pythonhosted.org/packages/e7/64/66f2f4d1b4f0007c6e9078bd95b609b633d3957fe6dd23eac33ebde4b584/propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0", size = 241124, upload-time = "2025-03-26T03:04:08.676Z" }, - { url = "https://files.pythonhosted.org/packages/aa/bf/7b8c9fd097d511638fa9b6af3d986adbdf567598a567b46338c925144c1b/propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5", size = 240283, upload-time = "2025-03-26T03:04:10.172Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c9/e85aeeeaae83358e2a1ef32d6ff50a483a5d5248bc38510d030a6f4e2816/propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256", size = 232498, upload-time = "2025-03-26T03:04:11.616Z" }, - { url = "https://files.pythonhosted.org/packages/8e/66/acb88e1f30ef5536d785c283af2e62931cb934a56a3ecf39105887aa8905/propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073", size = 221486, upload-time = "2025-03-26T03:04:13.102Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f9/233ddb05ffdcaee4448508ee1d70aa7deff21bb41469ccdfcc339f871427/propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d", size = 222675, upload-time = "2025-03-26T03:04:14.658Z" }, - { url = "https://files.pythonhosted.org/packages/98/b8/eb977e28138f9e22a5a789daf608d36e05ed93093ef12a12441030da800a/propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f", size = 215727, upload-time = "2025-03-26T03:04:16.207Z" }, - { url = "https://files.pythonhosted.org/packages/89/2d/5f52d9c579f67b8ee1edd9ec073c91b23cc5b7ff7951a1e449e04ed8fdf3/propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0", size = 217878, upload-time = "2025-03-26T03:04:18.11Z" }, - { url = "https://files.pythonhosted.org/packages/7a/fd/5283e5ed8a82b00c7a989b99bb6ea173db1ad750bf0bf8dff08d3f4a4e28/propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a", size = 230558, upload-time = "2025-03-26T03:04:19.562Z" }, - { url = "https://files.pythonhosted.org/packages/90/38/ab17d75938ef7ac87332c588857422ae126b1c76253f0f5b1242032923ca/propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a", size = 233754, upload-time = "2025-03-26T03:04:21.065Z" }, - { url = "https://files.pythonhosted.org/packages/06/5d/3b921b9c60659ae464137508d3b4c2b3f52f592ceb1964aa2533b32fcf0b/propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9", size = 226088, upload-time = "2025-03-26T03:04:22.718Z" }, - { url = "https://files.pythonhosted.org/packages/54/6e/30a11f4417d9266b5a464ac5a8c5164ddc9dd153dfa77bf57918165eb4ae/propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005", size = 40859, upload-time = "2025-03-26T03:04:24.039Z" }, - { url = "https://files.pythonhosted.org/packages/1d/3a/8a68dd867da9ca2ee9dfd361093e9cb08cb0f37e5ddb2276f1b5177d7731/propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7", size = 45153, upload-time = "2025-03-26T03:04:25.211Z" }, - { url = "https://files.pythonhosted.org/packages/41/aa/ca78d9be314d1e15ff517b992bebbed3bdfef5b8919e85bf4940e57b6137/propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723", size = 80430, upload-time = "2025-03-26T03:04:26.436Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d8/f0c17c44d1cda0ad1979af2e593ea290defdde9eaeb89b08abbe02a5e8e1/propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976", size = 46637, upload-time = "2025-03-26T03:04:27.932Z" }, - { url = "https://files.pythonhosted.org/packages/ae/bd/c1e37265910752e6e5e8a4c1605d0129e5b7933c3dc3cf1b9b48ed83b364/propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b", size = 46123, upload-time = "2025-03-26T03:04:30.659Z" }, - { url = "https://files.pythonhosted.org/packages/d4/b0/911eda0865f90c0c7e9f0415d40a5bf681204da5fd7ca089361a64c16b28/propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f", size = 243031, upload-time = "2025-03-26T03:04:31.977Z" }, - { url = "https://files.pythonhosted.org/packages/0a/06/0da53397c76a74271621807265b6eb61fb011451b1ddebf43213df763669/propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70", size = 249100, upload-time = "2025-03-26T03:04:33.45Z" }, - { url = "https://files.pythonhosted.org/packages/f1/eb/13090e05bf6b963fc1653cdc922133ced467cb4b8dab53158db5a37aa21e/propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7", size = 250170, upload-time = "2025-03-26T03:04:35.542Z" }, - { url = "https://files.pythonhosted.org/packages/3b/4c/f72c9e1022b3b043ec7dc475a0f405d4c3e10b9b1d378a7330fecf0652da/propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25", size = 245000, upload-time = "2025-03-26T03:04:37.501Z" }, - { url = "https://files.pythonhosted.org/packages/e8/fd/970ca0e22acc829f1adf5de3724085e778c1ad8a75bec010049502cb3a86/propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277", size = 230262, upload-time = "2025-03-26T03:04:39.532Z" }, - { url = "https://files.pythonhosted.org/packages/c4/42/817289120c6b9194a44f6c3e6b2c3277c5b70bbad39e7df648f177cc3634/propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8", size = 236772, upload-time = "2025-03-26T03:04:41.109Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9c/3b3942b302badd589ad6b672da3ca7b660a6c2f505cafd058133ddc73918/propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e", size = 231133, upload-time = "2025-03-26T03:04:42.544Z" }, - { url = "https://files.pythonhosted.org/packages/98/a1/75f6355f9ad039108ff000dfc2e19962c8dea0430da9a1428e7975cf24b2/propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee", size = 230741, upload-time = "2025-03-26T03:04:44.06Z" }, - { url = "https://files.pythonhosted.org/packages/67/0c/3e82563af77d1f8731132166da69fdfd95e71210e31f18edce08a1eb11ea/propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815", size = 244047, upload-time = "2025-03-26T03:04:45.983Z" }, - { url = "https://files.pythonhosted.org/packages/f7/50/9fb7cca01532a08c4d5186d7bb2da6c4c587825c0ae134b89b47c7d62628/propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5", size = 246467, upload-time = "2025-03-26T03:04:47.699Z" }, - { url = "https://files.pythonhosted.org/packages/a9/02/ccbcf3e1c604c16cc525309161d57412c23cf2351523aedbb280eb7c9094/propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7", size = 241022, upload-time = "2025-03-26T03:04:49.195Z" }, - { url = "https://files.pythonhosted.org/packages/db/19/e777227545e09ca1e77a6e21274ae9ec45de0f589f0ce3eca2a41f366220/propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b", size = 40647, upload-time = "2025-03-26T03:04:50.595Z" }, - { url = "https://files.pythonhosted.org/packages/24/bb/3b1b01da5dd04c77a204c84e538ff11f624e31431cfde7201d9110b092b1/propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3", size = 44784, upload-time = "2025-03-26T03:04:51.791Z" }, - { url = "https://files.pythonhosted.org/packages/58/60/f645cc8b570f99be3cf46714170c2de4b4c9d6b827b912811eff1eb8a412/propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8", size = 77865, upload-time = "2025-03-26T03:04:53.406Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d4/c1adbf3901537582e65cf90fd9c26fde1298fde5a2c593f987112c0d0798/propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f", size = 45452, upload-time = "2025-03-26T03:04:54.624Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b5/fe752b2e63f49f727c6c1c224175d21b7d1727ce1d4873ef1c24c9216830/propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111", size = 44800, upload-time = "2025-03-26T03:04:55.844Z" }, - { url = "https://files.pythonhosted.org/packages/62/37/fc357e345bc1971e21f76597028b059c3d795c5ca7690d7a8d9a03c9708a/propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5", size = 225804, upload-time = "2025-03-26T03:04:57.158Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f1/16e12c33e3dbe7f8b737809bad05719cff1dccb8df4dafbcff5575002c0e/propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb", size = 230650, upload-time = "2025-03-26T03:04:58.61Z" }, - { url = "https://files.pythonhosted.org/packages/3e/a2/018b9f2ed876bf5091e60153f727e8f9073d97573f790ff7cdf6bc1d1fb8/propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7", size = 234235, upload-time = "2025-03-26T03:05:00.599Z" }, - { url = "https://files.pythonhosted.org/packages/45/5f/3faee66fc930dfb5da509e34c6ac7128870631c0e3582987fad161fcb4b1/propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120", size = 228249, upload-time = "2025-03-26T03:05:02.11Z" }, - { url = "https://files.pythonhosted.org/packages/62/1e/a0d5ebda5da7ff34d2f5259a3e171a94be83c41eb1e7cd21a2105a84a02e/propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654", size = 214964, upload-time = "2025-03-26T03:05:03.599Z" }, - { url = "https://files.pythonhosted.org/packages/db/a0/d72da3f61ceab126e9be1f3bc7844b4e98c6e61c985097474668e7e52152/propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e", size = 222501, upload-time = "2025-03-26T03:05:05.107Z" }, - { url = "https://files.pythonhosted.org/packages/18/6d/a008e07ad7b905011253adbbd97e5b5375c33f0b961355ca0a30377504ac/propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b", size = 217917, upload-time = "2025-03-26T03:05:06.59Z" }, - { url = "https://files.pythonhosted.org/packages/98/37/02c9343ffe59e590e0e56dc5c97d0da2b8b19fa747ebacf158310f97a79a/propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53", size = 217089, upload-time = "2025-03-26T03:05:08.1Z" }, - { url = "https://files.pythonhosted.org/packages/53/1b/d3406629a2c8a5666d4674c50f757a77be119b113eedd47b0375afdf1b42/propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5", size = 228102, upload-time = "2025-03-26T03:05:09.982Z" }, - { url = "https://files.pythonhosted.org/packages/cd/a7/3664756cf50ce739e5f3abd48febc0be1a713b1f389a502ca819791a6b69/propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7", size = 230122, upload-time = "2025-03-26T03:05:11.408Z" }, - { url = "https://files.pythonhosted.org/packages/35/36/0bbabaacdcc26dac4f8139625e930f4311864251276033a52fd52ff2a274/propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef", size = 226818, upload-time = "2025-03-26T03:05:12.909Z" }, - { url = "https://files.pythonhosted.org/packages/cc/27/4e0ef21084b53bd35d4dae1634b6d0bad35e9c58ed4f032511acca9d4d26/propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24", size = 40112, upload-time = "2025-03-26T03:05:14.289Z" }, - { url = "https://files.pythonhosted.org/packages/a6/2c/a54614d61895ba6dd7ac8f107e2b2a0347259ab29cbf2ecc7b94fa38c4dc/propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037", size = 44034, upload-time = "2025-03-26T03:05:15.616Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a8/0a4fd2f664fc6acc66438370905124ce62e84e2e860f2557015ee4a61c7e/propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f", size = 82613, upload-time = "2025-03-26T03:05:16.913Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e5/5ef30eb2cd81576256d7b6caaa0ce33cd1d2c2c92c8903cccb1af1a4ff2f/propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c", size = 47763, upload-time = "2025-03-26T03:05:18.607Z" }, - { url = "https://files.pythonhosted.org/packages/87/9a/87091ceb048efeba4d28e903c0b15bcc84b7c0bf27dc0261e62335d9b7b8/propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc", size = 47175, upload-time = "2025-03-26T03:05:19.85Z" }, - { url = "https://files.pythonhosted.org/packages/3e/2f/854e653c96ad1161f96194c6678a41bbb38c7947d17768e8811a77635a08/propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de", size = 292265, upload-time = "2025-03-26T03:05:21.654Z" }, - { url = "https://files.pythonhosted.org/packages/40/8d/090955e13ed06bc3496ba4a9fb26c62e209ac41973cb0d6222de20c6868f/propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6", size = 294412, upload-time = "2025-03-26T03:05:23.147Z" }, - { url = "https://files.pythonhosted.org/packages/39/e6/d51601342e53cc7582449e6a3c14a0479fab2f0750c1f4d22302e34219c6/propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7", size = 294290, upload-time = "2025-03-26T03:05:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/3b/4d/be5f1a90abc1881884aa5878989a1acdafd379a91d9c7e5e12cef37ec0d7/propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458", size = 282926, upload-time = "2025-03-26T03:05:26.459Z" }, - { url = "https://files.pythonhosted.org/packages/57/2b/8f61b998c7ea93a2b7eca79e53f3e903db1787fca9373af9e2cf8dc22f9d/propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11", size = 267808, upload-time = "2025-03-26T03:05:28.188Z" }, - { url = "https://files.pythonhosted.org/packages/11/1c/311326c3dfce59c58a6098388ba984b0e5fb0381ef2279ec458ef99bd547/propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c", size = 290916, upload-time = "2025-03-26T03:05:29.757Z" }, - { url = "https://files.pythonhosted.org/packages/4b/74/91939924b0385e54dc48eb2e4edd1e4903ffd053cf1916ebc5347ac227f7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf", size = 262661, upload-time = "2025-03-26T03:05:31.472Z" }, - { url = "https://files.pythonhosted.org/packages/c2/d7/e6079af45136ad325c5337f5dd9ef97ab5dc349e0ff362fe5c5db95e2454/propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27", size = 264384, upload-time = "2025-03-26T03:05:32.984Z" }, - { url = "https://files.pythonhosted.org/packages/b7/d5/ba91702207ac61ae6f1c2da81c5d0d6bf6ce89e08a2b4d44e411c0bbe867/propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757", size = 291420, upload-time = "2025-03-26T03:05:34.496Z" }, - { url = "https://files.pythonhosted.org/packages/58/70/2117780ed7edcd7ba6b8134cb7802aada90b894a9810ec56b7bb6018bee7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18", size = 290880, upload-time = "2025-03-26T03:05:36.256Z" }, - { url = "https://files.pythonhosted.org/packages/4a/1f/ecd9ce27710021ae623631c0146719280a929d895a095f6d85efb6a0be2e/propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a", size = 287407, upload-time = "2025-03-26T03:05:37.799Z" }, - { url = "https://files.pythonhosted.org/packages/3e/66/2e90547d6b60180fb29e23dc87bd8c116517d4255240ec6d3f7dc23d1926/propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d", size = 42573, upload-time = "2025-03-26T03:05:39.193Z" }, - { url = "https://files.pythonhosted.org/packages/cb/8f/50ad8599399d1861b4d2b6b45271f0ef6af1b09b0a2386a46dbaf19c9535/propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e", size = 46757, upload-time = "2025-03-26T03:05:40.811Z" }, - { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" }, -] - [[package]] name = "ptyprocess" version = "0.7.0" @@ -1207,6 +945,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/93/dba5ed08c2e31ec7cdc2ce75705a484ef0be1a2fecac8a58272489349de8/setuptools-80.4.0-py3-none-any.whl", hash = "sha256:6cdc8cb9a7d590b237dbe4493614a9b75d0559b888047c1f67d49ba50fc3edb2", size = 1200812, upload-time = "2025-05-09T20:42:25.325Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "stack-data" version = "0.6.3" @@ -1278,18 +1025,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] -[[package]] -name = "tqdm" -version = "4.67.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, -] - [[package]] name = "traitlets" version = "5.14.3" @@ -1319,6 +1054,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/ec/00f9d5fd040ae29867355e559a94e9a8429225a0284a3f5f091a3878bfc0/twine-5.1.1-py3-none-any.whl", hash = "sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997", size = 38650, upload-time = "2024-06-26T15:00:43.825Z" }, ] +[[package]] +name = "typer" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/ca/950278884e2ca20547ff3eb109478c6baf6b8cf219318e6bc4f666fad8e8/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca", size = 104755, upload-time = "2025-09-23T09:47:48.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" }, +] + +[[package]] +name = "types-tabulate" +version = "0.9.0.20241207" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/43/16030404a327e4ff8c692f2273854019ed36718667b2993609dc37d14dd4/types_tabulate-0.9.0.20241207.tar.gz", hash = "sha256:ac1ac174750c0a385dfd248edc6279fa328aaf4ea317915ab879a2ec47833230", size = 8195, upload-time = "2024-12-07T02:54:42.554Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/86/a9ebfd509cbe74471106dffed320e208c72537f9aeb0a55eaa6b1b5e4d17/types_tabulate-0.9.0.20241207-py3-none-any.whl", hash = "sha256:b8dad1343c2a8ba5861c5441370c3e35908edd234ff036d4298708a1d4cf8a85", size = 8307, upload-time = "2024-12-07T02:54:41.031Z" }, +] + [[package]] name = "types-tqdm" version = "4.64.7.9" @@ -1376,88 +1135,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/d6/003e593296a85fd6ed616ed962795b2f87709c3eee2bca4f6d0fe55c6d00/wheel-0.37.1-py2.py3-none-any.whl", hash = "sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a", size = 35301, upload-time = "2021-12-22T12:22:00.742Z" }, ] -[[package]] -name = "yarl" -version = "1.20.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/62/51/c0edba5219027f6eab262e139f73e2417b0f4efffa23bf562f6e18f76ca5/yarl-1.20.0.tar.gz", hash = "sha256:686d51e51ee5dfe62dec86e4866ee0e9ed66df700d55c828a615640adc885307", size = 185258, upload-time = "2025-04-17T00:45:14.661Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/82/a59d8e21b20ffc836775fa7daedac51d16bb8f3010c4fcb495c4496aa922/yarl-1.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fdb5204d17cb32b2de2d1e21c7461cabfacf17f3645e4b9039f210c5d3378bf3", size = 145178, upload-time = "2025-04-17T00:42:04.511Z" }, - { url = "https://files.pythonhosted.org/packages/ba/81/315a3f6f95947cfbf37c92d6fbce42a1a6207b6c38e8c2b452499ec7d449/yarl-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eaddd7804d8e77d67c28d154ae5fab203163bd0998769569861258e525039d2a", size = 96859, upload-time = "2025-04-17T00:42:06.43Z" }, - { url = "https://files.pythonhosted.org/packages/ad/17/9b64e575583158551b72272a1023cdbd65af54fe13421d856b2850a6ddb7/yarl-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:634b7ba6b4a85cf67e9df7c13a7fb2e44fa37b5d34501038d174a63eaac25ee2", size = 94647, upload-time = "2025-04-17T00:42:07.976Z" }, - { url = "https://files.pythonhosted.org/packages/2c/29/8f291e7922a58a21349683f6120a85701aeefaa02e9f7c8a2dc24fe3f431/yarl-1.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d409e321e4addf7d97ee84162538c7258e53792eb7c6defd0c33647d754172e", size = 355788, upload-time = "2025-04-17T00:42:09.902Z" }, - { url = "https://files.pythonhosted.org/packages/26/6d/b4892c80b805c42c228c6d11e03cafabf81662d371b0853e7f0f513837d5/yarl-1.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ea52f7328a36960ba3231c6677380fa67811b414798a6e071c7085c57b6d20a9", size = 344613, upload-time = "2025-04-17T00:42:11.768Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0e/517aa28d3f848589bae9593717b063a544b86ba0a807d943c70f48fcf3bb/yarl-1.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8703517b924463994c344dcdf99a2d5ce9eca2b6882bb640aa555fb5efc706a", size = 370953, upload-time = "2025-04-17T00:42:13.983Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/5bd09d2f1ad6e6f7c2beae9e50db78edd2cca4d194d227b958955573e240/yarl-1.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:077989b09ffd2f48fb2d8f6a86c5fef02f63ffe6b1dd4824c76de7bb01e4f2e2", size = 369204, upload-time = "2025-04-17T00:42:16.386Z" }, - { url = "https://files.pythonhosted.org/packages/9c/85/d793a703cf4bd0d4cd04e4b13cc3d44149470f790230430331a0c1f52df5/yarl-1.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0acfaf1da020253f3533526e8b7dd212838fdc4109959a2c53cafc6db611bff2", size = 358108, upload-time = "2025-04-17T00:42:18.622Z" }, - { url = "https://files.pythonhosted.org/packages/6f/54/b6c71e13549c1f6048fbc14ce8d930ac5fb8bafe4f1a252e621a24f3f1f9/yarl-1.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4230ac0b97ec5eeb91d96b324d66060a43fd0d2a9b603e3327ed65f084e41f8", size = 346610, upload-time = "2025-04-17T00:42:20.9Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1a/d6087d58bdd0d8a2a37bbcdffac9d9721af6ebe50d85304d9f9b57dfd862/yarl-1.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a6a1e6ae21cdd84011c24c78d7a126425148b24d437b5702328e4ba640a8902", size = 365378, upload-time = "2025-04-17T00:42:22.926Z" }, - { url = "https://files.pythonhosted.org/packages/02/84/e25ddff4cbc001dbc4af76f8d41a3e23818212dd1f0a52044cbc60568872/yarl-1.20.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:86de313371ec04dd2531f30bc41a5a1a96f25a02823558ee0f2af0beaa7ca791", size = 356919, upload-time = "2025-04-17T00:42:25.145Z" }, - { url = "https://files.pythonhosted.org/packages/04/76/898ae362353bf8f64636495d222c8014c8e5267df39b1a9fe1e1572fb7d0/yarl-1.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dd59c9dd58ae16eaa0f48c3d0cbe6be8ab4dc7247c3ff7db678edecbaf59327f", size = 364248, upload-time = "2025-04-17T00:42:27.475Z" }, - { url = "https://files.pythonhosted.org/packages/1b/b0/9d9198d83a622f1c40fdbf7bd13b224a6979f2e1fc2cf50bfb1d8773c495/yarl-1.20.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a0bc5e05f457b7c1994cc29e83b58f540b76234ba6b9648a4971ddc7f6aa52da", size = 378418, upload-time = "2025-04-17T00:42:29.333Z" }, - { url = "https://files.pythonhosted.org/packages/c7/ce/1f50c1cc594cf5d3f5bf4a9b616fca68680deaec8ad349d928445ac52eb8/yarl-1.20.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c9471ca18e6aeb0e03276b5e9b27b14a54c052d370a9c0c04a68cefbd1455eb4", size = 383850, upload-time = "2025-04-17T00:42:31.668Z" }, - { url = "https://files.pythonhosted.org/packages/89/1e/a59253a87b35bfec1a25bb5801fb69943330b67cfd266278eb07e0609012/yarl-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40ed574b4df723583a26c04b298b283ff171bcc387bc34c2683235e2487a65a5", size = 381218, upload-time = "2025-04-17T00:42:33.523Z" }, - { url = "https://files.pythonhosted.org/packages/85/b0/26f87df2b3044b0ef1a7cf66d321102bdca091db64c5ae853fcb2171c031/yarl-1.20.0-cp311-cp311-win32.whl", hash = "sha256:db243357c6c2bf3cd7e17080034ade668d54ce304d820c2a58514a4e51d0cfd6", size = 86606, upload-time = "2025-04-17T00:42:35.873Z" }, - { url = "https://files.pythonhosted.org/packages/33/46/ca335c2e1f90446a77640a45eeb1cd8f6934f2c6e4df7db0f0f36ef9f025/yarl-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:8c12cd754d9dbd14204c328915e23b0c361b88f3cffd124129955e60a4fbfcfb", size = 93374, upload-time = "2025-04-17T00:42:37.586Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e8/3efdcb83073df978bb5b1a9cc0360ce596680e6c3fac01f2a994ccbb8939/yarl-1.20.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e06b9f6cdd772f9b665e5ba8161968e11e403774114420737f7884b5bd7bdf6f", size = 147089, upload-time = "2025-04-17T00:42:39.602Z" }, - { url = "https://files.pythonhosted.org/packages/60/c3/9e776e98ea350f76f94dd80b408eaa54e5092643dbf65fd9babcffb60509/yarl-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9ae2fbe54d859b3ade40290f60fe40e7f969d83d482e84d2c31b9bff03e359e", size = 97706, upload-time = "2025-04-17T00:42:41.469Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5b/45cdfb64a3b855ce074ae607b9fc40bc82e7613b94e7612b030255c93a09/yarl-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d12b8945250d80c67688602c891237994d203d42427cb14e36d1a732eda480e", size = 95719, upload-time = "2025-04-17T00:42:43.666Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4e/929633b249611eeed04e2f861a14ed001acca3ef9ec2a984a757b1515889/yarl-1.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:087e9731884621b162a3e06dc0d2d626e1542a617f65ba7cc7aeab279d55ad33", size = 343972, upload-time = "2025-04-17T00:42:45.391Z" }, - { url = "https://files.pythonhosted.org/packages/49/fd/047535d326c913f1a90407a3baf7ff535b10098611eaef2c527e32e81ca1/yarl-1.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69df35468b66c1a6e6556248e6443ef0ec5f11a7a4428cf1f6281f1879220f58", size = 339639, upload-time = "2025-04-17T00:42:47.552Z" }, - { url = "https://files.pythonhosted.org/packages/48/2f/11566f1176a78f4bafb0937c0072410b1b0d3640b297944a6a7a556e1d0b/yarl-1.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b2992fe29002fd0d4cbaea9428b09af9b8686a9024c840b8a2b8f4ea4abc16f", size = 353745, upload-time = "2025-04-17T00:42:49.406Z" }, - { url = "https://files.pythonhosted.org/packages/26/17/07dfcf034d6ae8837b33988be66045dd52f878dfb1c4e8f80a7343f677be/yarl-1.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c903e0b42aab48abfbac668b5a9d7b6938e721a6341751331bcd7553de2dcae", size = 354178, upload-time = "2025-04-17T00:42:51.588Z" }, - { url = "https://files.pythonhosted.org/packages/15/45/212604d3142d84b4065d5f8cab6582ed3d78e4cc250568ef2a36fe1cf0a5/yarl-1.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf099e2432131093cc611623e0b0bcc399b8cddd9a91eded8bfb50402ec35018", size = 349219, upload-time = "2025-04-17T00:42:53.674Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e0/a10b30f294111c5f1c682461e9459935c17d467a760c21e1f7db400ff499/yarl-1.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7f62f5dc70a6c763bec9ebf922be52aa22863d9496a9a30124d65b489ea672", size = 337266, upload-time = "2025-04-17T00:42:55.49Z" }, - { url = "https://files.pythonhosted.org/packages/33/a6/6efa1d85a675d25a46a167f9f3e80104cde317dfdf7f53f112ae6b16a60a/yarl-1.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:54ac15a8b60382b2bcefd9a289ee26dc0920cf59b05368c9b2b72450751c6eb8", size = 360873, upload-time = "2025-04-17T00:42:57.895Z" }, - { url = "https://files.pythonhosted.org/packages/77/67/c8ab718cb98dfa2ae9ba0f97bf3cbb7d45d37f13fe1fbad25ac92940954e/yarl-1.20.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:25b3bc0763a7aca16a0f1b5e8ef0f23829df11fb539a1b70476dcab28bd83da7", size = 360524, upload-time = "2025-04-17T00:43:00.094Z" }, - { url = "https://files.pythonhosted.org/packages/bd/e8/c3f18660cea1bc73d9f8a2b3ef423def8dadbbae6c4afabdb920b73e0ead/yarl-1.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b2586e36dc070fc8fad6270f93242124df68b379c3a251af534030a4a33ef594", size = 365370, upload-time = "2025-04-17T00:43:02.242Z" }, - { url = "https://files.pythonhosted.org/packages/c9/99/33f3b97b065e62ff2d52817155a89cfa030a1a9b43fee7843ef560ad9603/yarl-1.20.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:866349da9d8c5290cfefb7fcc47721e94de3f315433613e01b435473be63daa6", size = 373297, upload-time = "2025-04-17T00:43:04.189Z" }, - { url = "https://files.pythonhosted.org/packages/3d/89/7519e79e264a5f08653d2446b26d4724b01198a93a74d2e259291d538ab1/yarl-1.20.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:33bb660b390a0554d41f8ebec5cd4475502d84104b27e9b42f5321c5192bfcd1", size = 378771, upload-time = "2025-04-17T00:43:06.609Z" }, - { url = "https://files.pythonhosted.org/packages/3a/58/6c460bbb884abd2917c3eef6f663a4a873f8dc6f498561fc0ad92231c113/yarl-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737e9f171e5a07031cbee5e9180f6ce21a6c599b9d4b2c24d35df20a52fabf4b", size = 375000, upload-time = "2025-04-17T00:43:09.01Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/dd7ed1aa23fea996834278d7ff178f215b24324ee527df53d45e34d21d28/yarl-1.20.0-cp312-cp312-win32.whl", hash = "sha256:839de4c574169b6598d47ad61534e6981979ca2c820ccb77bf70f4311dd2cc64", size = 86355, upload-time = "2025-04-17T00:43:11.311Z" }, - { url = "https://files.pythonhosted.org/packages/ca/c6/333fe0338305c0ac1c16d5aa7cc4841208d3252bbe62172e0051006b5445/yarl-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d7dbbe44b443b0c4aa0971cb07dcb2c2060e4a9bf8d1301140a33a93c98e18c", size = 92904, upload-time = "2025-04-17T00:43:13.087Z" }, - { url = "https://files.pythonhosted.org/packages/0f/6f/514c9bff2900c22a4f10e06297714dbaf98707143b37ff0bcba65a956221/yarl-1.20.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2137810a20b933b1b1b7e5cf06a64c3ed3b4747b0e5d79c9447c00db0e2f752f", size = 145030, upload-time = "2025-04-17T00:43:15.083Z" }, - { url = "https://files.pythonhosted.org/packages/4e/9d/f88da3fa319b8c9c813389bfb3463e8d777c62654c7168e580a13fadff05/yarl-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:447c5eadd750db8389804030d15f43d30435ed47af1313303ed82a62388176d3", size = 96894, upload-time = "2025-04-17T00:43:17.372Z" }, - { url = "https://files.pythonhosted.org/packages/cd/57/92e83538580a6968b2451d6c89c5579938a7309d4785748e8ad42ddafdce/yarl-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42fbe577272c203528d402eec8bf4b2d14fd49ecfec92272334270b850e9cd7d", size = 94457, upload-time = "2025-04-17T00:43:19.431Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ee/7ee43bd4cf82dddd5da97fcaddb6fa541ab81f3ed564c42f146c83ae17ce/yarl-1.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e321617de4ab170226cd15006a565d0fa0d908f11f724a2c9142d6b2812ab0", size = 343070, upload-time = "2025-04-17T00:43:21.426Z" }, - { url = "https://files.pythonhosted.org/packages/4a/12/b5eccd1109e2097bcc494ba7dc5de156e41cf8309fab437ebb7c2b296ce3/yarl-1.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4345f58719825bba29895011e8e3b545e6e00257abb984f9f27fe923afca2501", size = 337739, upload-time = "2025-04-17T00:43:23.634Z" }, - { url = "https://files.pythonhosted.org/packages/7d/6b/0eade8e49af9fc2585552f63c76fa59ef469c724cc05b29519b19aa3a6d5/yarl-1.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d9b980d7234614bc4674468ab173ed77d678349c860c3af83b1fffb6a837ddc", size = 351338, upload-time = "2025-04-17T00:43:25.695Z" }, - { url = "https://files.pythonhosted.org/packages/45/cb/aaaa75d30087b5183c7b8a07b4fb16ae0682dd149a1719b3a28f54061754/yarl-1.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af4baa8a445977831cbaa91a9a84cc09debb10bc8391f128da2f7bd070fc351d", size = 353636, upload-time = "2025-04-17T00:43:27.876Z" }, - { url = "https://files.pythonhosted.org/packages/98/9d/d9cb39ec68a91ba6e66fa86d97003f58570327d6713833edf7ad6ce9dde5/yarl-1.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123393db7420e71d6ce40d24885a9e65eb1edefc7a5228db2d62bcab3386a5c0", size = 348061, upload-time = "2025-04-17T00:43:29.788Z" }, - { url = "https://files.pythonhosted.org/packages/72/6b/103940aae893d0cc770b4c36ce80e2ed86fcb863d48ea80a752b8bda9303/yarl-1.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab47acc9332f3de1b39e9b702d9c916af7f02656b2a86a474d9db4e53ef8fd7a", size = 334150, upload-time = "2025-04-17T00:43:31.742Z" }, - { url = "https://files.pythonhosted.org/packages/ef/b2/986bd82aa222c3e6b211a69c9081ba46484cffa9fab2a5235e8d18ca7a27/yarl-1.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4a34c52ed158f89876cba9c600b2c964dfc1ca52ba7b3ab6deb722d1d8be6df2", size = 362207, upload-time = "2025-04-17T00:43:34.099Z" }, - { url = "https://files.pythonhosted.org/packages/14/7c/63f5922437b873795d9422cbe7eb2509d4b540c37ae5548a4bb68fd2c546/yarl-1.20.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:04d8cfb12714158abf2618f792c77bc5c3d8c5f37353e79509608be4f18705c9", size = 361277, upload-time = "2025-04-17T00:43:36.202Z" }, - { url = "https://files.pythonhosted.org/packages/81/83/450938cccf732466953406570bdb42c62b5ffb0ac7ac75a1f267773ab5c8/yarl-1.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7dc63ad0d541c38b6ae2255aaa794434293964677d5c1ec5d0116b0e308031f5", size = 364990, upload-time = "2025-04-17T00:43:38.551Z" }, - { url = "https://files.pythonhosted.org/packages/b4/de/af47d3a47e4a833693b9ec8e87debb20f09d9fdc9139b207b09a3e6cbd5a/yarl-1.20.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d02b591a64e4e6ca18c5e3d925f11b559c763b950184a64cf47d74d7e41877", size = 374684, upload-time = "2025-04-17T00:43:40.481Z" }, - { url = "https://files.pythonhosted.org/packages/62/0b/078bcc2d539f1faffdc7d32cb29a2d7caa65f1a6f7e40795d8485db21851/yarl-1.20.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:95fc9876f917cac7f757df80a5dda9de59d423568460fe75d128c813b9af558e", size = 382599, upload-time = "2025-04-17T00:43:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/74/a9/4fdb1a7899f1fb47fd1371e7ba9e94bff73439ce87099d5dd26d285fffe0/yarl-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb769ae5760cd1c6a712135ee7915f9d43f11d9ef769cb3f75a23e398a92d384", size = 378573, upload-time = "2025-04-17T00:43:44.797Z" }, - { url = "https://files.pythonhosted.org/packages/fd/be/29f5156b7a319e4d2e5b51ce622b4dfb3aa8d8204cd2a8a339340fbfad40/yarl-1.20.0-cp313-cp313-win32.whl", hash = "sha256:70e0c580a0292c7414a1cead1e076c9786f685c1fc4757573d2967689b370e62", size = 86051, upload-time = "2025-04-17T00:43:47.076Z" }, - { url = "https://files.pythonhosted.org/packages/52/56/05fa52c32c301da77ec0b5f63d2d9605946fe29defacb2a7ebd473c23b81/yarl-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:4c43030e4b0af775a85be1fa0433119b1565673266a70bf87ef68a9d5ba3174c", size = 92742, upload-time = "2025-04-17T00:43:49.193Z" }, - { url = "https://files.pythonhosted.org/packages/d4/2f/422546794196519152fc2e2f475f0e1d4d094a11995c81a465faf5673ffd/yarl-1.20.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b6c4c3d0d6a0ae9b281e492b1465c72de433b782e6b5001c8e7249e085b69051", size = 163575, upload-time = "2025-04-17T00:43:51.533Z" }, - { url = "https://files.pythonhosted.org/packages/90/fc/67c64ddab6c0b4a169d03c637fb2d2a212b536e1989dec8e7e2c92211b7f/yarl-1.20.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8681700f4e4df891eafa4f69a439a6e7d480d64e52bf460918f58e443bd3da7d", size = 106121, upload-time = "2025-04-17T00:43:53.506Z" }, - { url = "https://files.pythonhosted.org/packages/6d/00/29366b9eba7b6f6baed7d749f12add209b987c4cfbfa418404dbadc0f97c/yarl-1.20.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:84aeb556cb06c00652dbf87c17838eb6d92cfd317799a8092cee0e570ee11229", size = 103815, upload-time = "2025-04-17T00:43:55.41Z" }, - { url = "https://files.pythonhosted.org/packages/28/f4/a2a4c967c8323c03689383dff73396281ced3b35d0ed140580825c826af7/yarl-1.20.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f166eafa78810ddb383e930d62e623d288fb04ec566d1b4790099ae0f31485f1", size = 408231, upload-time = "2025-04-17T00:43:57.825Z" }, - { url = "https://files.pythonhosted.org/packages/0f/a1/66f7ffc0915877d726b70cc7a896ac30b6ac5d1d2760613603b022173635/yarl-1.20.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5d3d6d14754aefc7a458261027a562f024d4f6b8a798adb472277f675857b1eb", size = 390221, upload-time = "2025-04-17T00:44:00.526Z" }, - { url = "https://files.pythonhosted.org/packages/41/15/cc248f0504610283271615e85bf38bc014224122498c2016d13a3a1b8426/yarl-1.20.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a8f64df8ed5d04c51260dbae3cc82e5649834eebea9eadfd829837b8093eb00", size = 411400, upload-time = "2025-04-17T00:44:02.853Z" }, - { url = "https://files.pythonhosted.org/packages/5c/af/f0823d7e092bfb97d24fce6c7269d67fcd1aefade97d0a8189c4452e4d5e/yarl-1.20.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d9949eaf05b4d30e93e4034a7790634bbb41b8be2d07edd26754f2e38e491de", size = 411714, upload-time = "2025-04-17T00:44:04.904Z" }, - { url = "https://files.pythonhosted.org/packages/83/70/be418329eae64b9f1b20ecdaac75d53aef098797d4c2299d82ae6f8e4663/yarl-1.20.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c366b254082d21cc4f08f522ac201d0d83a8b8447ab562732931d31d80eb2a5", size = 404279, upload-time = "2025-04-17T00:44:07.721Z" }, - { url = "https://files.pythonhosted.org/packages/19/f5/52e02f0075f65b4914eb890eea1ba97e6fd91dd821cc33a623aa707b2f67/yarl-1.20.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91bc450c80a2e9685b10e34e41aef3d44ddf99b3a498717938926d05ca493f6a", size = 384044, upload-time = "2025-04-17T00:44:09.708Z" }, - { url = "https://files.pythonhosted.org/packages/6a/36/b0fa25226b03d3f769c68d46170b3e92b00ab3853d73127273ba22474697/yarl-1.20.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c2aa4387de4bc3a5fe158080757748d16567119bef215bec643716b4fbf53f9", size = 416236, upload-time = "2025-04-17T00:44:11.734Z" }, - { url = "https://files.pythonhosted.org/packages/cb/3a/54c828dd35f6831dfdd5a79e6c6b4302ae2c5feca24232a83cb75132b205/yarl-1.20.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d2cbca6760a541189cf87ee54ff891e1d9ea6406079c66341008f7ef6ab61145", size = 402034, upload-time = "2025-04-17T00:44:13.975Z" }, - { url = "https://files.pythonhosted.org/packages/10/97/c7bf5fba488f7e049f9ad69c1b8fdfe3daa2e8916b3d321aa049e361a55a/yarl-1.20.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:798a5074e656f06b9fad1a162be5a32da45237ce19d07884d0b67a0aa9d5fdda", size = 407943, upload-time = "2025-04-17T00:44:16.052Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a4/022d2555c1e8fcff08ad7f0f43e4df3aba34f135bff04dd35d5526ce54ab/yarl-1.20.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f106e75c454288472dbe615accef8248c686958c2e7dd3b8d8ee2669770d020f", size = 423058, upload-time = "2025-04-17T00:44:18.547Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f6/0873a05563e5df29ccf35345a6ae0ac9e66588b41fdb7043a65848f03139/yarl-1.20.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3b60a86551669c23dc5445010534d2c5d8a4e012163218fc9114e857c0586fdd", size = 423792, upload-time = "2025-04-17T00:44:20.639Z" }, - { url = "https://files.pythonhosted.org/packages/9e/35/43fbbd082708fa42e923f314c24f8277a28483d219e049552e5007a9aaca/yarl-1.20.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e429857e341d5e8e15806118e0294f8073ba9c4580637e59ab7b238afca836f", size = 422242, upload-time = "2025-04-17T00:44:22.851Z" }, - { url = "https://files.pythonhosted.org/packages/ed/f7/f0f2500cf0c469beb2050b522c7815c575811627e6d3eb9ec7550ddd0bfe/yarl-1.20.0-cp313-cp313t-win32.whl", hash = "sha256:65a4053580fe88a63e8e4056b427224cd01edfb5f951498bfefca4052f0ce0ac", size = 93816, upload-time = "2025-04-17T00:44:25.491Z" }, - { url = "https://files.pythonhosted.org/packages/3f/93/f73b61353b2a699d489e782c3f5998b59f974ec3156a2050a52dfd7e8946/yarl-1.20.0-cp313-cp313t-win_amd64.whl", hash = "sha256:53b2da3a6ca0a541c1ae799c349788d480e5144cac47dba0266c7cb6c76151fe", size = 101093, upload-time = "2025-04-17T00:44:27.418Z" }, - { url = "https://files.pythonhosted.org/packages/ea/1f/70c57b3d7278e94ed22d85e09685d3f0a38ebdd8c5c73b65ba4c0d0fe002/yarl-1.20.0-py3-none-any.whl", hash = "sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124", size = 46124, upload-time = "2025-04-17T00:45:12.199Z" }, -] - [[package]] name = "zipp" version = "3.21.0" From 6af5067ca412206582cb9f48ab40553240f7ce87 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Wed, 10 Dec 2025 12:25:52 -0500 Subject: [PATCH 006/108] fix Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f3921a91..0b3ef4be 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ formatters: venv ## https://docs.astral.sh/ruff/formatter/#line-breaks uv tool run ruff format mypy: ## Typechecking with mypy - uv tool run mypy src/ + uv run mypy src/ test: venv install ## Run unit tests with coverage uv run -m pytest From d23c5265eee4bbd5328eb2408495494a912f0786 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Thu, 11 Dec 2025 01:04:05 -0500 Subject: [PATCH 007/108] Add scraper script to use with GHA to idenify new search fields --- src/fpds/scripts/scraper.py | 72 +++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/fpds/scripts/scraper.py diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py new file mode 100644 index 00000000..0d5f588f --- /dev/null +++ b/src/fpds/scripts/scraper.py @@ -0,0 +1,72 @@ +"""Scrapes fields from FPDS ezSearch page. + +author: derek663@gmail.com +last_updated: 2025-12-11 +""" + +from pathlib import Path +import json + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + +from fpds.config import FPDS_EZSEARCH_URL, FPDS_FIELDS_FILE_PATH + + +def update_fields_json(dropdown_fields): + with Path(FPDS_FIELDS_FILE_PATH).open(encoding="utf-8") as file: + config = json.load(file) + + current_field_options = [field["name"] for field in config] + + # as of right now, we have no way to validate the pattern unless we go to the data dict + new_options = [ + { + "description": "", + "name": field, + "quotes": False, + "regex": "", + } + for field in dropdown_fields if field not in current_field_options + ] + config.extend(new_options) + sorted_config = sorted(config, key=lambda field: field["name"]) + + with Path(FPDS_FIELDS_FILE_PATH).open(mode="w", encoding="utf-8") as file: + json.dump(sorted_config, file, indent=4) + +def scrape_ezsearch(): + driver = webdriver.Chrome() + driver.get(FPDS_EZSEARCH_URL) + + search_criteria_button = driver.find_element( + By.CSS_SELECTOR, + "input[title='Advanced Search Criteria']", + ) + search_criteria_button.click() + + advanced_search_div = WebDriverWait(driver, 10).until( + EC.visibility_of_element_located((By.ID, "advancedSearchdiv")) + ) + + add_button = advanced_search_div.find_element(By.CSS_SELECTOR, "input[title='Add']") + add_button.click() + + dropdowns = WebDriverWait(driver, 10).until( + EC.presence_of_all_elements_located((By.CSS_SELECTOR, "#advancedSearchdiv select")) + ) + + dropdown_fields = [] + for dropdown in dropdowns: + options = dropdown.find_elements(By.TAG_NAME, "option") + for opt in options: + dropdown_fields.append(opt.get_attribute('value')) + + return dropdown_fields + +if __name__ == "__main__": + dropdown_fields = scrape_ezsearch() + update_fields_json(dropdown_fields=dropdown_fields) From 1959eb80d431be3bfd75bd898defb6c4ef9a5c78 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 01:55:57 -0500 Subject: [PATCH 008/108] Fix dependencies; remove aiohttp --> httpx --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b75b9c32..5aabf7d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ classifiers = [ dependencies = [ "httpx==0.28.1", "typer==0.19.2", + "typing-extensions>=4.15.0", ] [project.urls] @@ -27,6 +28,9 @@ Issues = "https://github.com/dherincx92/fpds/issues" cli = [ "tabulate==0.9.0", ] +scripts = [ + "selenium>=4.0.0,<5.0.0", +] dev = [ "ipdb==0.13.9", "ipython==8.5.0", @@ -54,7 +58,7 @@ all = [ ] [project.scripts] -fpds = "fpds.cli.root:app" +fpds = "fpds.cli:app" [tool.pytest.ini_options] pythonpath = ["."] From ba867a2ce149a746f39b45530c3e7fcf68bfe305 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 01:56:50 -0500 Subject: [PATCH 009/108] Add WIP parser tests; still few enhancements remaining in parser --- src/fpds/core/parser.py | 18 +++++----- tests/test_parser.py | 73 +++++++++++++++++++++++++---------------- 2 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index a1b1932d..3af93d91 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -166,7 +166,7 @@ async def convert( self, client: AsyncClient, link: str, - semaphore: asyncio.Semaphore, + semaphore: Semaphore, ) -> fpdsSubTree: """Retrieves content from FPDS ATOM feed as a SubTree instance.""" async with semaphore: @@ -232,6 +232,13 @@ async def iter_data(self) -> AsyncGenerator[FPDS_ENTRY, None]: ------ `FPDS_ENTRY` A single FPDS record as it becomes available. + + Example + ------- + >>> gen = request.iter_data() + >>> records = [] + >>> async for entry in gen: + >>> records.append(entry) """ from concurrent.futures import as_completed @@ -239,14 +246,7 @@ async def iter_data(self) -> AsyncGenerator[FPDS_ENTRY, None]: data = await self.fetch() # List[fpdsSubTree] with ProcessPoolExecutor(max_workers=num_processes) as pool: - with Progress( - SpinnerColumn(), - TextColumn("[progress.description]{task.description}"), - BarColumn(), - TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), - TextColumn("({task.completed}/{task.total})"), - TimeRemainingColumn(elapsed_when_finished=True), - ) as progress: + with self._create_progress() as progress: task_id = progress.add_task( "Processing records...", total=len(data) ) diff --git a/tests/test_parser.py b/tests/test_parser.py index 1b80f438..6db4714b 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,12 +1,11 @@ import unittest -from unittest import TestCase +from unittest import TestCase, mock from xml.etree.ElementTree import ElementTree, fromstring -import pytest - from fpds import fpdsRequest from fpds.errors import ( fpdsInvalidParameter, + fpdsMaxPageLengthExceededError, fpdsMismatchedParameterRegexError, fpdsMissingKeywordParameterError, ) @@ -34,14 +33,15 @@ CONTENT_TREE = ElementTree(fromstring(FULL_RESPONSE_DATA_BYTES)) -class MockResponse(object): - def __init__(self, status_code): - self.status_code = status_code +class MockHTTPResponse: + def read(self): + return FULL_RESPONSE_DATA_BYTES - def raise_for_status(self): - if self.status_code != 200: - raise Exception + def __enter__(self): + return self + def __exit__(self, exc_type, exc_value, traceback): + pass class MockFpdsXML(object): def pagination_links(self, params="some-param1: param1-value"): @@ -53,29 +53,46 @@ def pagination_links(self, params="some-param1: param1-value"): class TestFpdsRequest(TestCase): - def setUp(self): - self._class = fpdsRequest(**FPDS_REQUEST_PARAMS_DICT) - def test_params_exist(self): - with pytest.raises(fpdsMissingKeywordParameterError): + with self.assertRaises(fpdsMissingKeywordParameterError): fpdsRequest({}) - def test_invalid_param(self): - with pytest.raises(fpdsInvalidParameter): - fpdsRequest(**FPDS_REQUEST_INVALID_PARAM_DICT) - - def test_invalid_param_regex(self): - with pytest.raises(fpdsMismatchedParameterRegexError): - fpdsRequest(**FPDS_REQUEST_INVALID_REGEX_DICT) - - def test_str_magic_method(self): - object_as_string = ( - '' + @mock.patch("fpds.core.parser.urlopen") + def test_request_link_count(self, mock_urlopen): + """Test that generated number of links from initial request is correct.""" + mock_urlopen.return_value = MockHTTPResponse() + req = fpdsRequest(**FPDS_REQUEST_PARAMS_DICT) + self.assertEqual(len(req.links), 3) + + @mock.patch("fpds.core.parser.fpdsRequest.page_index") + @mock.patch("fpds.core.parser.urlopen") + def test_request_link_count(self, mock_urlopen, mock_page_index): + """Test that page_index is called once when page is provided.""" + mock_urlopen.return_value = MockHTTPResponse() + fpdsRequest( + **FPDS_REQUEST_PARAMS_DICT, + page=1, ) - self.assertEqual(self._class.__str__(), object_as_string) - - def test_search_params_property(self): - self.assertEqual(FPDS_SEARCH_PARAMS_PROPERTY, self._class.search_params) + mock_page_index.assert_called_once() + + @mock.patch("fpds.core.parser.urlopen") + def test_max_page_length_exceeded_error_raised(self, mock_urlopen): + mock_urlopen.return_value = MockHTTPResponse() + with self.assertRaises(fpdsMaxPageLengthExceededError): + fpdsRequest( + **FPDS_REQUEST_PARAMS_DICT, + page=1_000_000, + ) + + + @mock.patch("fpds.core.parser.urlopen") + def test_skip_regex_validation_warning_raised(self, mock_urlopen): + mock_urlopen.return_value = MockHTTPResponse() + with self.assertWarns(UserWarning): + fpdsRequest( + **FPDS_REQUEST_PARAMS_DICT, + skip_regex_validation=True, + ) if __name__ == "__main__": From 22c42becec89dd05ab2e56c9643a805bb4874a0a Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 02:00:52 -0500 Subject: [PATCH 010/108] Fix trailing comma on param utilities --- src/fpds/utilities/params.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/fpds/utilities/params.py b/src/fpds/utilities/params.py index d269b69b..c7c018b3 100644 --- a/src/fpds/utilities/params.py +++ b/src/fpds/utilities/params.py @@ -6,7 +6,7 @@ """ import re -from typing import Any, Dict, List, Optional, TypedDict, Union, cast +from typing import Any, Dict, List, Optional, TypedDict, cast from fpds.config import FPDS_FIELDS_CONFIG as FIELDS from fpds.errors import ( @@ -26,7 +26,8 @@ class ParameterConfig(TypedDict): def get_search_param_from_config( - name: str, config: CONFIG_TYPE = FIELDS + name: str, + config: CONFIG_TYPE = FIELDS, ) -> ParameterConfig: """Finds the name of a kwarg in `fields.json`.""" field_config = [field for field in config if field.get("name") == name] From 5fbfb61b113ec582fdaaa21d4fb0853521645bf8 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 02:01:27 -0500 Subject: [PATCH 011/108] fix docstring for param utilities --- src/fpds/utilities/params.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpds/utilities/params.py b/src/fpds/utilities/params.py index c7c018b3..06b2d10c 100644 --- a/src/fpds/utilities/params.py +++ b/src/fpds/utilities/params.py @@ -2,7 +2,7 @@ Utility functions related to FPDS request parameters author: derek663@gmail.com -last_updated: 01/20/2024 +last_updated: 12/14/2024 """ import re From be51cfb1c901ff0122e7a7c4ba3afd9d72cc14f2 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 02:19:35 -0500 Subject: [PATCH 012/108] Add new fpdsWriter to handle chunking and gzipping of records --- src/fpds/core/parser.py | 77 ++++++++++++------------------------ src/fpds/utilities/writer.py | 56 ++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 52 deletions(-) create mode 100644 src/fpds/utilities/writer.py diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index 3af93d91..a71af0b7 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -7,8 +7,6 @@ """ import asyncio -import gzip -import json import multiprocessing from pathlib import Path import warnings @@ -32,8 +30,12 @@ from fpds.core import FPDS_ENTRY from fpds.core.mixins import fpdsMixin from fpds.core.xml import fpdsSubTree, fpdsTree -from fpds.errors import fpdsMaxPageLengthExceededError, fpdsMissingKeywordParameterError +from fpds.errors import ( + fpdsMaxPageLengthExceededError, + fpdsMissingKeywordParameterError, +) from fpds.utilities import validate_kwarg +from fpds.utilities.writer import fpdsChunkWriter from fpds.config import FPDS_DATA_DATE_DIR @@ -155,6 +157,10 @@ def page_count(self) -> int: """Total number of FPDS pages contained in request.""" return len(self.links) + @staticmethod + def mb_to_bytes(self) -> int: + return self.max_chunk_size_mb * 1_048_576 + def initial_request(self) -> bytes: """Returns the root XML tree from the initial request.""" encoded_params = parse.urlencode({"q": self.search_params}) @@ -250,13 +256,10 @@ async def iter_data(self) -> AsyncGenerator[FPDS_ENTRY, None]: task_id = progress.add_task( "Processing records...", total=len(data) ) - - # Submit all tasks future_to_record = { pool.submit(self._jsonify, record): record for record in data } - # Process results as they complete and yield immediately for future in as_completed(future_to_record): progress.update(task_id, advance=1) result = future.result() @@ -264,53 +267,23 @@ async def iter_data(self) -> AsyncGenerator[FPDS_ENTRY, None]: yield entry - async def data( - self, - output_dir: Path = FPDS_DATA_DATE_DIR, - ) -> None: - run_id = str(uuid4()) + async def data(self, output_dir: Path = FPDS_DATA_DATE_DIR) -> None: + """Outputs FPDS data as partitioned-sized JSON gzip files. - # Memory-efficient chunking: write to gzip files + Parameters + ---------- + output_dir: `Path` + The directory to output the FPDS data to. + Defaults to `~/.fpds/`. + """ + run_id = str(uuid4()) output_path = (Path(output_dir) / run_id).expanduser() output_path.mkdir(parents=True, exist_ok=True) - max_bytes = self.max_chunk_size_mb * 1_048_576 # Convert MB to bytes - file_paths: List[str] = [] - chunk_buffer: List[FPDS_ENTRY] = [] - current_size = 0 - chunk_index = 0 - - async for entry in self.iter_data(): - # Convert entry to JSON string to measure size - entry_json = json.dumps(entry) - entry_size = len(entry_json.encode("utf-8")) - - # Check if adding this entry would exceed the chunk size - if current_size + entry_size > max_bytes and chunk_buffer: - # Write current buffer to gzip file - file_path = output_path / f"{uuid4()}.json.gz" - file_paths.append(str(file_path)) - - with gzip.open(file_path, "wt", encoding="utf-8") as gz_file: - json.dump(chunk_buffer, gz_file) - - # Reset buffer for next chunk - chunk_buffer = [entry] - current_size = entry_size - chunk_index += 1 - else: - # Add entry to current buffer - chunk_buffer.append(entry) - current_size += entry_size - - # Write final chunk if there are remaining records - - if chunk_buffer: - file_path = output_path / f"{uuid4()}.json.gz" - file_paths.append(str(file_path)) - - with gzip.open(file_path, "wt", encoding="utf-8") as gz_file: - json.dump(chunk_buffer, gz_file) - - - print(f"Wrote {len(file_paths)} chunks to {output_path}") + writer = fpdsChunkWriter( + output_dir=output_path, + max_chunk_size_mb=self.max_chunk_size_mb, + ) + file_paths = await writer.chunkify(self.iter_data()) + print(f"Wrote records to {output_path}") + return file_paths diff --git a/src/fpds/utilities/writer.py b/src/fpds/utilities/writer.py new file mode 100644 index 00000000..218f1f28 --- /dev/null +++ b/src/fpds/utilities/writer.py @@ -0,0 +1,56 @@ +""" +Outputs FPDS data into partitioned-sized JSON gzip files. + +author: derek663@gmail.com +last_updated: 12/14/2025 +""" + +import gzip +import json +from pathlib import Path +from typing import List +from uuid import uuid4 + +from fpds.core import FPDS_ENTRY + +class fpdsChunkWriter: + def __init__( + self, + output_dir: Path, + max_chunk_size_mb: int, + ) -> None: + self.output_dir = output_dir + self.max_bytes = max_chunk_size_mb * 1_048_576 + self.output_dir.mkdir(parents=True, exist_ok=True) + + @staticmethod + def record_size(entry: FPDS_ENTRY) -> int: + entry_json = json.dumps(entry) + return len(entry_json.encode("utf-8")) + + async def chunkify(self, entries): + file_paths: list[Path] = [] + chunk: List[FPDS_ENTRY] = [] + current_size = 0 + + async for entry in entries: + size = self.record_size(entry) + + if current_size + size > self.max_bytes and chunk: + file_paths.append(self.flush(chunk)) + chunk = [entry] + current_size = size + else: + chunk.append(entry) + current_size += size + + if chunk: + file_paths.append(self.flush(chunk)) + + return file_paths + + + def flush(self, chunk_buffer: list[FPDS_ENTRY]) -> Path: + file_path = self.output_dir / f"{uuid4()}.json.gz" + with gzip.open(file_path, "wt", encoding="utf-8") as gz_file: + json.dump(chunk_buffer, gz_file) From e88e84434d1c1158f9bbf521dd850fe26d5450ce Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 02:19:52 -0500 Subject: [PATCH 013/108] Updated uv.lock dependencies --- uv.lock | 927 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 621 insertions(+), 306 deletions(-) diff --git a/uv.lock b/uv.lock index 175b3402..b901b385 100644 --- a/uv.lock +++ b/uv.lock @@ -26,20 +26,20 @@ wheels = [ [[package]] name = "asttokens" -version = "3.0.0" +version = "3.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, ] [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] @@ -76,104 +76,156 @@ wheels = [ [[package]] name = "certifi" -version = "2025.4.26" +version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, - { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, - { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, - { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, - { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, - { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, - { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, - { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, - { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, - { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, - { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, - { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, - { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, - { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, - { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, - { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, - { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, - { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, - { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, - { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, - { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, - { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, - { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, - { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, - { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, - { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, - { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, - { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] [[package]] name = "click" -version = "8.1.8" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] @@ -187,52 +239,89 @@ wheels = [ [[package]] name = "coverage" -version = "7.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload-time = "2025-03-30T20:35:12.286Z" }, - { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload-time = "2025-03-30T20:35:14.18Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload-time = "2025-03-30T20:35:15.616Z" }, - { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245, upload-time = "2025-03-30T20:35:18.648Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032, upload-time = "2025-03-30T20:35:20.131Z" }, - { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679, upload-time = "2025-03-30T20:35:21.636Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852, upload-time = "2025-03-30T20:35:23.525Z" }, - { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389, upload-time = "2025-03-30T20:35:25.09Z" }, - { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997, upload-time = "2025-03-30T20:35:26.914Z" }, - { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911, upload-time = "2025-03-30T20:35:28.498Z" }, - { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload-time = "2025-03-30T20:35:29.959Z" }, - { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload-time = "2025-03-30T20:35:31.912Z" }, - { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload-time = "2025-03-30T20:35:33.455Z" }, - { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload-time = "2025-03-30T20:35:35.354Z" }, - { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload-time = "2025-03-30T20:35:37.121Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload-time = "2025-03-30T20:35:39.07Z" }, - { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload-time = "2025-03-30T20:35:40.598Z" }, - { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload-time = "2025-03-30T20:35:42.204Z" }, - { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload-time = "2025-03-30T20:35:44.216Z" }, - { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload-time = "2025-03-30T20:35:45.797Z" }, - { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload-time = "2025-03-30T20:35:47.417Z" }, - { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload-time = "2025-03-30T20:35:49.002Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload-time = "2025-03-30T20:35:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload-time = "2025-03-30T20:35:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload-time = "2025-03-30T20:35:54.658Z" }, - { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload-time = "2025-03-30T20:35:56.221Z" }, - { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload-time = "2025-03-30T20:35:57.801Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload-time = "2025-03-30T20:35:59.378Z" }, - { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload-time = "2025-03-30T20:36:01.005Z" }, - { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload-time = "2025-03-30T20:36:03.006Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload-time = "2025-03-30T20:36:04.638Z" }, - { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload-time = "2025-03-30T20:36:06.503Z" }, - { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload-time = "2025-03-30T20:36:08.137Z" }, - { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload-time = "2025-03-30T20:36:09.781Z" }, - { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload-time = "2025-03-30T20:36:11.409Z" }, - { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload-time = "2025-03-30T20:36:13.86Z" }, - { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload-time = "2025-03-30T20:36:16.074Z" }, - { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload-time = "2025-03-30T20:36:18.033Z" }, - { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload-time = "2025-03-30T20:36:19.644Z" }, - { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload-time = "2025-03-30T20:36:21.282Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload-time = "2025-03-30T20:36:41.959Z" }, - { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" }, +version = "7.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/45/2c665ca77ec32ad67e25c77daf1cee28ee4558f3bc571cdbaf88a00b9f23/coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936", size = 820905, upload-time = "2025-12-08T13:14:38.055Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/dc/888bf90d8b1c3d0b4020a40e52b9f80957d75785931ec66c7dfaccc11c7d/coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820", size = 218104, upload-time = "2025-12-08T13:12:33.333Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ea/069d51372ad9c380214e86717e40d1a743713a2af191cfba30a0911b0a4a/coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f", size = 218606, upload-time = "2025-12-08T13:12:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/68/09/77b1c3a66c2aa91141b6c4471af98e5b1ed9b9e6d17255da5eb7992299e3/coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96", size = 248999, upload-time = "2025-12-08T13:12:36.02Z" }, + { url = "https://files.pythonhosted.org/packages/0a/32/2e2f96e9d5691eaf1181d9040f850b8b7ce165ea10810fd8e2afa534cef7/coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259", size = 250925, upload-time = "2025-12-08T13:12:37.221Z" }, + { url = "https://files.pythonhosted.org/packages/7b/45/b88ddac1d7978859b9a39a8a50ab323186148f1d64bc068f86fc77706321/coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb", size = 253032, upload-time = "2025-12-08T13:12:38.763Z" }, + { url = "https://files.pythonhosted.org/packages/71/cb/e15513f94c69d4820a34b6bf3d2b1f9f8755fa6021be97c7065442d7d653/coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9", size = 249134, upload-time = "2025-12-08T13:12:40.382Z" }, + { url = "https://files.pythonhosted.org/packages/09/61/d960ff7dc9e902af3310ce632a875aaa7860f36d2bc8fc8b37ee7c1b82a5/coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030", size = 250731, upload-time = "2025-12-08T13:12:41.992Z" }, + { url = "https://files.pythonhosted.org/packages/98/34/c7c72821794afc7c7c2da1db8f00c2c98353078aa7fb6b5ff36aac834b52/coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833", size = 248795, upload-time = "2025-12-08T13:12:43.331Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5b/e0f07107987a43b2def9aa041c614ddb38064cbf294a71ef8c67d43a0cdd/coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8", size = 248514, upload-time = "2025-12-08T13:12:44.546Z" }, + { url = "https://files.pythonhosted.org/packages/71/c2/c949c5d3b5e9fc6dd79e1b73cdb86a59ef14f3709b1d72bf7668ae12e000/coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753", size = 249424, upload-time = "2025-12-08T13:12:45.759Z" }, + { url = "https://files.pythonhosted.org/packages/11/f1/bbc009abd6537cec0dffb2cc08c17a7f03de74c970e6302db4342a6e05af/coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b", size = 220597, upload-time = "2025-12-08T13:12:47.378Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/d9977f2fb51c10fbaed0718ce3d0a8541185290b981f73b1d27276c12d91/coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe", size = 221536, upload-time = "2025-12-08T13:12:48.7Z" }, + { url = "https://files.pythonhosted.org/packages/be/ad/3fcf43fd96fb43e337a3073dea63ff148dcc5c41ba7a14d4c7d34efb2216/coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7", size = 220206, upload-time = "2025-12-08T13:12:50.365Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f1/2619559f17f31ba00fc40908efd1fbf1d0a5536eb75dc8341e7d660a08de/coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf", size = 218274, upload-time = "2025-12-08T13:12:52.095Z" }, + { url = "https://files.pythonhosted.org/packages/2b/11/30d71ae5d6e949ff93b2a79a2c1b4822e00423116c5c6edfaeef37301396/coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f", size = 218638, upload-time = "2025-12-08T13:12:53.418Z" }, + { url = "https://files.pythonhosted.org/packages/79/c2/fce80fc6ded8d77e53207489d6065d0fed75db8951457f9213776615e0f5/coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb", size = 250129, upload-time = "2025-12-08T13:12:54.744Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b6/51b5d1eb6fcbb9a1d5d6984e26cbe09018475c2922d554fd724dd0f056ee/coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621", size = 252885, upload-time = "2025-12-08T13:12:56.401Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/972a5affea41de798691ab15d023d3530f9f56a72e12e243f35031846ff7/coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74", size = 253974, upload-time = "2025-12-08T13:12:57.718Z" }, + { url = "https://files.pythonhosted.org/packages/8a/56/116513aee860b2c7968aa3506b0f59b22a959261d1dbf3aea7b4450a7520/coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57", size = 250538, upload-time = "2025-12-08T13:12:59.254Z" }, + { url = "https://files.pythonhosted.org/packages/d6/75/074476d64248fbadf16dfafbf93fdcede389ec821f74ca858d7c87d2a98c/coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8", size = 251912, upload-time = "2025-12-08T13:13:00.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d2/aa4f8acd1f7c06024705c12609d8698c51b27e4d635d717cd1934c9668e2/coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d", size = 250054, upload-time = "2025-12-08T13:13:01.892Z" }, + { url = "https://files.pythonhosted.org/packages/19/98/8df9e1af6a493b03694a1e8070e024e7d2cdc77adedc225a35e616d505de/coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b", size = 249619, upload-time = "2025-12-08T13:13:03.236Z" }, + { url = "https://files.pythonhosted.org/packages/d8/71/f8679231f3353018ca66ef647fa6fe7b77e6bff7845be54ab84f86233363/coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd", size = 251496, upload-time = "2025-12-08T13:13:04.511Z" }, + { url = "https://files.pythonhosted.org/packages/04/86/9cb406388034eaf3c606c22094edbbb82eea1fa9d20c0e9efadff20d0733/coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef", size = 220808, upload-time = "2025-12-08T13:13:06.422Z" }, + { url = "https://files.pythonhosted.org/packages/1c/59/af483673df6455795daf5f447c2f81a3d2fcfc893a22b8ace983791f6f34/coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae", size = 221616, upload-time = "2025-12-08T13:13:07.95Z" }, + { url = "https://files.pythonhosted.org/packages/64/b0/959d582572b30a6830398c60dd419c1965ca4b5fb38ac6b7093a0d50ca8d/coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080", size = 220261, upload-time = "2025-12-08T13:13:09.581Z" }, + { url = "https://files.pythonhosted.org/packages/7c/cc/bce226595eb3bf7d13ccffe154c3c487a22222d87ff018525ab4dd2e9542/coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf", size = 218297, upload-time = "2025-12-08T13:13:10.977Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9f/73c4d34600aae03447dff3d7ad1d0ac649856bfb87d1ca7d681cfc913f9e/coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a", size = 218673, upload-time = "2025-12-08T13:13:12.562Z" }, + { url = "https://files.pythonhosted.org/packages/63/ab/8fa097db361a1e8586535ae5073559e6229596b3489ec3ef2f5b38df8cb2/coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74", size = 249652, upload-time = "2025-12-08T13:13:13.909Z" }, + { url = "https://files.pythonhosted.org/packages/90/3a/9bfd4de2ff191feb37ef9465855ca56a6f2f30a3bca172e474130731ac3d/coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6", size = 252251, upload-time = "2025-12-08T13:13:15.553Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/b5d8105f016e1b5874af0d7c67542da780ccd4a5f2244a433d3e20ceb1ad/coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b", size = 253492, upload-time = "2025-12-08T13:13:16.849Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b8/0fad449981803cc47a4694768b99823fb23632150743f9c83af329bb6090/coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232", size = 249850, upload-time = "2025-12-08T13:13:18.142Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e9/8d68337c3125014d918cf4327d5257553a710a2995a6a6de2ac77e5aa429/coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971", size = 251633, upload-time = "2025-12-08T13:13:19.56Z" }, + { url = "https://files.pythonhosted.org/packages/55/14/d4112ab26b3a1bc4b3c1295d8452dcf399ed25be4cf649002fb3e64b2d93/coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d", size = 249586, upload-time = "2025-12-08T13:13:20.883Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a9/22b0000186db663b0d82f86c2f1028099ae9ac202491685051e2a11a5218/coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137", size = 249412, upload-time = "2025-12-08T13:13:22.22Z" }, + { url = "https://files.pythonhosted.org/packages/a1/2e/42d8e0d9e7527fba439acdc6ed24a2b97613b1dc85849b1dd935c2cffef0/coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511", size = 251191, upload-time = "2025-12-08T13:13:23.899Z" }, + { url = "https://files.pythonhosted.org/packages/a4/af/8c7af92b1377fd8860536aadd58745119252aaaa71a5213e5a8e8007a9f5/coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1", size = 220829, upload-time = "2025-12-08T13:13:25.182Z" }, + { url = "https://files.pythonhosted.org/packages/58/f9/725e8bf16f343d33cbe076c75dc8370262e194ff10072c0608b8e5cf33a3/coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a", size = 221640, upload-time = "2025-12-08T13:13:26.836Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ff/e98311000aa6933cc79274e2b6b94a2fe0fe3434fca778eba82003675496/coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6", size = 220269, upload-time = "2025-12-08T13:13:28.116Z" }, + { url = "https://files.pythonhosted.org/packages/cf/cf/bbaa2e1275b300343ea865f7d424cc0a2e2a1df6925a070b2b2d5d765330/coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a", size = 218990, upload-time = "2025-12-08T13:13:29.463Z" }, + { url = "https://files.pythonhosted.org/packages/21/1d/82f0b3323b3d149d7672e7744c116e9c170f4957e0c42572f0366dbb4477/coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8", size = 219340, upload-time = "2025-12-08T13:13:31.524Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/fe3fd4702a3832a255f4d43013eacb0ef5fc155a5960ea9269d8696db28b/coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053", size = 260638, upload-time = "2025-12-08T13:13:32.965Z" }, + { url = "https://files.pythonhosted.org/packages/ad/01/63186cb000307f2b4da463f72af9b85d380236965574c78e7e27680a2593/coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071", size = 262705, upload-time = "2025-12-08T13:13:34.378Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a1/c0dacef0cc865f2455d59eed3548573ce47ed603205ffd0735d1d78b5906/coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e", size = 265125, upload-time = "2025-12-08T13:13:35.73Z" }, + { url = "https://files.pythonhosted.org/packages/ef/92/82b99223628b61300bd382c205795533bed021505eab6dd86e11fb5d7925/coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493", size = 259844, upload-time = "2025-12-08T13:13:37.69Z" }, + { url = "https://files.pythonhosted.org/packages/cf/2c/89b0291ae4e6cd59ef042708e1c438e2290f8c31959a20055d8768349ee2/coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0", size = 262700, upload-time = "2025-12-08T13:13:39.525Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f9/a5f992efae1996245e796bae34ceb942b05db275e4b34222a9a40b9fbd3b/coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e", size = 260321, upload-time = "2025-12-08T13:13:41.172Z" }, + { url = "https://files.pythonhosted.org/packages/4c/89/a29f5d98c64fedbe32e2ac3c227fbf78edc01cc7572eee17d61024d89889/coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c", size = 259222, upload-time = "2025-12-08T13:13:43.282Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c3/940fe447aae302a6701ee51e53af7e08b86ff6eed7631e5740c157ee22b9/coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e", size = 261411, upload-time = "2025-12-08T13:13:44.72Z" }, + { url = "https://files.pythonhosted.org/packages/eb/31/12a4aec689cb942a89129587860ed4d0fd522d5fda81237147fde554b8ae/coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46", size = 221505, upload-time = "2025-12-08T13:13:46.332Z" }, + { url = "https://files.pythonhosted.org/packages/65/8c/3b5fe3259d863572d2b0827642c50c3855d26b3aefe80bdc9eba1f0af3b0/coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39", size = 222569, upload-time = "2025-12-08T13:13:47.79Z" }, + { url = "https://files.pythonhosted.org/packages/b0/39/f71fa8316a96ac72fc3908839df651e8eccee650001a17f2c78cdb355624/coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e", size = 220841, upload-time = "2025-12-08T13:13:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4b/9b54bedda55421449811dcd5263a2798a63f48896c24dfb92b0f1b0845bd/coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256", size = 218343, upload-time = "2025-12-08T13:13:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/59/df/c3a1f34d4bba2e592c8979f924da4d3d4598b0df2392fbddb7761258e3dc/coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a", size = 218672, upload-time = "2025-12-08T13:13:52.284Z" }, + { url = "https://files.pythonhosted.org/packages/07/62/eec0659e47857698645ff4e6ad02e30186eb8afd65214fd43f02a76537cb/coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9", size = 249715, upload-time = "2025-12-08T13:13:53.791Z" }, + { url = "https://files.pythonhosted.org/packages/23/2d/3c7ff8b2e0e634c1f58d095f071f52ed3c23ff25be524b0ccae8b71f99f8/coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19", size = 252225, upload-time = "2025-12-08T13:13:55.274Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ac/fb03b469d20e9c9a81093575003f959cf91a4a517b783aab090e4538764b/coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be", size = 253559, upload-time = "2025-12-08T13:13:57.161Z" }, + { url = "https://files.pythonhosted.org/packages/29/62/14afa9e792383c66cc0a3b872a06ded6e4ed1079c7d35de274f11d27064e/coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb", size = 249724, upload-time = "2025-12-08T13:13:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/31/b7/333f3dab2939070613696ab3ee91738950f0467778c6e5a5052e840646b7/coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8", size = 251582, upload-time = "2025-12-08T13:14:00.642Z" }, + { url = "https://files.pythonhosted.org/packages/81/cb/69162bda9381f39b2287265d7e29ee770f7c27c19f470164350a38318764/coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b", size = 249538, upload-time = "2025-12-08T13:14:02.556Z" }, + { url = "https://files.pythonhosted.org/packages/e0/76/350387b56a30f4970abe32b90b2a434f87d29f8b7d4ae40d2e8a85aacfb3/coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9", size = 249349, upload-time = "2025-12-08T13:14:04.015Z" }, + { url = "https://files.pythonhosted.org/packages/86/0d/7f6c42b8d59f4c7e43ea3059f573c0dcfed98ba46eb43c68c69e52ae095c/coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927", size = 251011, upload-time = "2025-12-08T13:14:05.505Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f1/4bb2dff379721bb0b5c649d5c5eaf438462cad824acf32eb1b7ca0c7078e/coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f", size = 221091, upload-time = "2025-12-08T13:14:07.127Z" }, + { url = "https://files.pythonhosted.org/packages/ba/44/c239da52f373ce379c194b0ee3bcc121020e397242b85f99e0afc8615066/coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc", size = 221904, upload-time = "2025-12-08T13:14:08.542Z" }, + { url = "https://files.pythonhosted.org/packages/89/1f/b9f04016d2a29c2e4a0307baefefad1a4ec5724946a2b3e482690486cade/coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b", size = 220480, upload-time = "2025-12-08T13:14:10.958Z" }, + { url = "https://files.pythonhosted.org/packages/16/d4/364a1439766c8e8647860584171c36010ca3226e6e45b1753b1b249c5161/coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28", size = 219074, upload-time = "2025-12-08T13:14:13.345Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f4/71ba8be63351e099911051b2089662c03d5671437a0ec2171823c8e03bec/coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe", size = 219342, upload-time = "2025-12-08T13:14:15.02Z" }, + { url = "https://files.pythonhosted.org/packages/5e/25/127d8ed03d7711a387d96f132589057213e3aef7475afdaa303412463f22/coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657", size = 260713, upload-time = "2025-12-08T13:14:16.907Z" }, + { url = "https://files.pythonhosted.org/packages/fd/db/559fbb6def07d25b2243663b46ba9eb5a3c6586c0c6f4e62980a68f0ee1c/coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff", size = 262825, upload-time = "2025-12-08T13:14:18.68Z" }, + { url = "https://files.pythonhosted.org/packages/37/99/6ee5bf7eff884766edb43bd8736b5e1c5144d0fe47498c3779326fe75a35/coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3", size = 265233, upload-time = "2025-12-08T13:14:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/d8/90/92f18fe0356ea69e1f98f688ed80cec39f44e9f09a1f26a1bbf017cc67f2/coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b", size = 259779, upload-time = "2025-12-08T13:14:22.367Z" }, + { url = "https://files.pythonhosted.org/packages/90/5d/b312a8b45b37a42ea7d27d7d3ff98ade3a6c892dd48d1d503e773503373f/coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d", size = 262700, upload-time = "2025-12-08T13:14:24.309Z" }, + { url = "https://files.pythonhosted.org/packages/63/f8/b1d0de5c39351eb71c366f872376d09386640840a2e09b0d03973d791e20/coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e", size = 260302, upload-time = "2025-12-08T13:14:26.068Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7c/d42f4435bc40c55558b3109a39e2d456cddcec37434f62a1f1230991667a/coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940", size = 259136, upload-time = "2025-12-08T13:14:27.604Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d3/23413241dc04d47cfe19b9a65b32a2edd67ecd0b817400c2843ebc58c847/coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2", size = 261467, upload-time = "2025-12-08T13:14:29.09Z" }, + { url = "https://files.pythonhosted.org/packages/13/e6/6e063174500eee216b96272c0d1847bf215926786f85c2bd024cf4d02d2f/coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7", size = 221875, upload-time = "2025-12-08T13:14:31.106Z" }, + { url = "https://files.pythonhosted.org/packages/3b/46/f4fb293e4cbe3620e3ac2a3e8fd566ed33affb5861a9b20e3dd6c1896cbc/coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc", size = 222982, upload-time = "2025-12-08T13:14:33.1Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/5b3b9018215ed9733fbd1ae3b2ed75c5de62c3b55377a52cae732e1b7805/coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a", size = 221016, upload-time = "2025-12-08T13:14:34.601Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4c/1968f32fb9a2604645827e11ff84a31e59d532e01995f904723b4f5328b3/coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904", size = 210068, upload-time = "2025-12-08T13:14:36.236Z" }, ] [package.optional-dependencies] @@ -242,35 +331,50 @@ toml = [ [[package]] name = "cryptography" -version = "44.0.3" +version = "46.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096, upload-time = "2025-05-02T19:36:04.667Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/06/af2cf8d56ef87c77319e9086601bef621bedf40f6f59069e1b6d1ec498c5/cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137", size = 3959305, upload-time = "2025-05-02T19:34:53.042Z" }, - { url = "https://files.pythonhosted.org/packages/ae/01/80de3bec64627207d030f47bf3536889efee8913cd363e78ca9a09b13c8e/cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c", size = 4171040, upload-time = "2025-05-02T19:34:54.675Z" }, - { url = "https://files.pythonhosted.org/packages/bd/48/bb16b7541d207a19d9ae8b541c70037a05e473ddc72ccb1386524d4f023c/cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76", size = 3963411, upload-time = "2025-05-02T19:34:56.61Z" }, - { url = "https://files.pythonhosted.org/packages/42/b2/7d31f2af5591d217d71d37d044ef5412945a8a8e98d5a2a8ae4fd9cd4489/cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359", size = 3689263, upload-time = "2025-05-02T19:34:58.591Z" }, - { url = "https://files.pythonhosted.org/packages/25/50/c0dfb9d87ae88ccc01aad8eb93e23cfbcea6a6a106a9b63a7b14c1f93c75/cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43", size = 4196198, upload-time = "2025-05-02T19:35:00.988Z" }, - { url = "https://files.pythonhosted.org/packages/66/c9/55c6b8794a74da652690c898cb43906310a3e4e4f6ee0b5f8b3b3e70c441/cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01", size = 3966502, upload-time = "2025-05-02T19:35:03.091Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f7/7cb5488c682ca59a02a32ec5f975074084db4c983f849d47b7b67cc8697a/cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d", size = 4196173, upload-time = "2025-05-02T19:35:05.018Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0b/2f789a8403ae089b0b121f8f54f4a3e5228df756e2146efdf4a09a3d5083/cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904", size = 4087713, upload-time = "2025-05-02T19:35:07.187Z" }, - { url = "https://files.pythonhosted.org/packages/1d/aa/330c13655f1af398fc154089295cf259252f0ba5df93b4bc9d9c7d7f843e/cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44", size = 4299064, upload-time = "2025-05-02T19:35:08.879Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f0/7491d44bba8d28b464a5bc8cc709f25a51e3eac54c0a4444cf2473a57c37/cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759", size = 3960307, upload-time = "2025-05-02T19:35:15.917Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c8/e5c5d0e1364d3346a5747cdcd7ecbb23ca87e6dea4f942a44e88be349f06/cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645", size = 4170876, upload-time = "2025-05-02T19:35:18.138Z" }, - { url = "https://files.pythonhosted.org/packages/73/96/025cb26fc351d8c7d3a1c44e20cf9a01e9f7cf740353c9c7a17072e4b264/cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2", size = 3964127, upload-time = "2025-05-02T19:35:19.864Z" }, - { url = "https://files.pythonhosted.org/packages/01/44/eb6522db7d9f84e8833ba3bf63313f8e257729cf3a8917379473fcfd6601/cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54", size = 3689164, upload-time = "2025-05-02T19:35:21.449Z" }, - { url = "https://files.pythonhosted.org/packages/68/fb/d61a4defd0d6cee20b1b8a1ea8f5e25007e26aeb413ca53835f0cae2bcd1/cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93", size = 4198081, upload-time = "2025-05-02T19:35:23.187Z" }, - { url = "https://files.pythonhosted.org/packages/1b/50/457f6911d36432a8811c3ab8bd5a6090e8d18ce655c22820994913dd06ea/cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c", size = 3967716, upload-time = "2025-05-02T19:35:25.426Z" }, - { url = "https://files.pythonhosted.org/packages/35/6e/dca39d553075980ccb631955c47b93d87d27f3596da8d48b1ae81463d915/cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f", size = 4197398, upload-time = "2025-05-02T19:35:27.678Z" }, - { url = "https://files.pythonhosted.org/packages/9b/9d/d1f2fe681eabc682067c66a74addd46c887ebacf39038ba01f8860338d3d/cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5", size = 4087900, upload-time = "2025-05-02T19:35:29.312Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f5/3599e48c5464580b73b236aafb20973b953cd2e7b44c7c2533de1d888446/cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b", size = 4301067, upload-time = "2025-05-02T19:35:31.547Z" }, - { url = "https://files.pythonhosted.org/packages/58/11/0a6bf45d53b9b2290ea3cec30e78b78e6ca29dc101e2e296872a0ffe1335/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647", size = 3895216, upload-time = "2025-05-02T19:35:51.351Z" }, - { url = "https://files.pythonhosted.org/packages/0a/27/b28cdeb7270e957f0077a2c2bfad1b38f72f1f6d699679f97b816ca33642/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259", size = 4115044, upload-time = "2025-05-02T19:35:53.044Z" }, - { url = "https://files.pythonhosted.org/packages/35/b0/ec4082d3793f03cb248881fecefc26015813199b88f33e3e990a43f79835/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff", size = 3898034, upload-time = "2025-05-02T19:35:54.72Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7f/adf62e0b8e8d04d50c9a91282a57628c00c54d4ae75e2b02a223bd1f2613/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5", size = 4114449, upload-time = "2025-05-02T19:35:57.139Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, + { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, + { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, + { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, + { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, + { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, + { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, ] [[package]] @@ -284,20 +388,20 @@ wheels = [ [[package]] name = "docutils" -version = "0.21.2" +version = "0.22.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/02/111134bfeb6e6c7ac4c74594e39a59f6c0195dc4846afbeac3cba60f1927/docutils-0.22.3.tar.gz", hash = "sha256:21486ae730e4ca9f622677b1412b879af1791efcfba517e4c6f60be543fc8cdd", size = 2290153, upload-time = "2025-11-06T02:35:55.655Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, + { url = "https://files.pythonhosted.org/packages/11/a8/c6a4b901d17399c77cd81fb001ce8961e9f5e04d3daf27e8925cb012e163/docutils-0.22.3-py3-none-any.whl", hash = "sha256:bd772e4aca73aff037958d44f2be5229ded4c09927fcf8690c577b66234d6ceb", size = 633032, upload-time = "2025-11-06T02:35:52.391Z" }, ] [[package]] name = "executing" -version = "2.2.0" +version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] [[package]] @@ -306,6 +410,7 @@ source = { editable = "." } dependencies = [ { name = "httpx" }, { name = "typer" }, + { name = "typing-extensions" }, ] [package.optional-dependencies] @@ -342,6 +447,9 @@ packaging = [ { name = "twine" }, { name = "wheel" }, ] +scripts = [ + { name = "selenium" }, +] tests = [ { name = "pytest" }, { name = "pytest-cov" }, @@ -363,15 +471,17 @@ requires-dist = [ { name = "pytest-cov", marker = "extra == 'tests'", specifier = "==3.0.0" }, { name = "pytest-runner", marker = "extra == 'tests'", specifier = "==6.0.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = "==0.12.1" }, + { name = "selenium", marker = "extra == 'scripts'", specifier = ">=4.0.0,<5.0.0" }, { name = "tabulate", marker = "extra == 'cli'", specifier = "==0.9.0" }, { name = "twine", marker = "extra == 'packaging'", specifier = "==5.1.1" }, { name = "typer", specifier = "==0.19.2" }, { name = "types-tabulate", marker = "extra == 'dev'", specifier = "==0.9.0.20241207" }, { name = "types-tqdm", marker = "extra == 'dev'", specifier = "==4.64.7.9" }, + { name = "typing-extensions", specifier = ">=4.15.0" }, { name = "versioningit", marker = "extra == 'dev'", specifier = ">=3.0.0,<4.0.0" }, { name = "wheel", marker = "extra == 'packaging'", specifier = "==0.37.1" }, ] -provides-extras = ["cli", "dev", "tests", "packaging", "all"] +provides-extras = ["cli", "scripts", "dev", "tests", "packaging", "all"] [[package]] name = "h11" @@ -412,11 +522,11 @@ wheels = [ [[package]] name = "idna" -version = "3.10" +version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] @@ -433,11 +543,11 @@ wheels = [ [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] @@ -501,14 +611,14 @@ wheels = [ [[package]] name = "jaraco-functools" -version = "4.1.0" +version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/23/9894b3df5d0a6eb44611c36aec777823fc2e07740dabbd0b810e19594013/jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d", size = 19159, upload-time = "2024-09-27T19:47:09.122Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/ed/1aa2d585304ec07262e1a83a9889880701079dde796ac7b1d1826f40c63d/jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294", size = 19755, upload-time = "2025-08-18T20:05:09.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/4f/24b319316142c44283d7540e76c7b5a6dbd5db623abd86bb7b3491c21018/jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649", size = 10187, upload-time = "2024-09-27T19:47:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", size = 10408, upload-time = "2025-08-18T20:05:08.69Z" }, ] [[package]] @@ -534,7 +644,7 @@ wheels = [ [[package]] name = "keyring" -version = "25.6.0" +version = "25.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, @@ -545,33 +655,96 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750, upload-time = "2024-12-25T15:26:45.782Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + +[[package]] +name = "librt" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/d9/6f3d3fcf5e5543ed8a60cc70fa7d50508ed60b8a10e9af6d2058159ab54e/librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798", size = 144549, upload-time = "2025-12-06T19:04:45.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/e6/f6391f5c6f158d31ed9af6bd1b1bcd3ffafdea1d816bc4219d0d90175a7f/librt-0.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:687403cced6a29590e6be6964463835315905221d797bc5c934a98750fe1a9af", size = 54711, upload-time = "2025-12-06T19:03:24.6Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1b/53c208188c178987c081560a0fcf36f5ca500d5e21769596c845ef2f40d4/librt-0.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24d70810f6e2ea853ff79338001533716b373cc0f63e2a0be5bc96129edb5fb5", size = 56664, upload-time = "2025-12-06T19:03:25.969Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5c/d9da832b9a1e5f8366e8a044ec80217945385b26cb89fd6f94bfdc7d80b0/librt-0.7.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf8c7735fbfc0754111f00edda35cf9e98a8d478de6c47b04eaa9cef4300eaa7", size = 161701, upload-time = "2025-12-06T19:03:27.035Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/1e0a7aba15e78529dd21f233076b876ee58c8b8711b1793315bdd3b263b0/librt-0.7.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32d43610dff472eab939f4d7fbdd240d1667794192690433672ae22d7af8445", size = 171040, upload-time = "2025-12-06T19:03:28.482Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/3cfa325c1c2bc25775ec6ec1718cfbec9cff4ac767d37d2d3a2d1cc6f02c/librt-0.7.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:adeaa886d607fb02563c1f625cf2ee58778a2567c0c109378da8f17ec3076ad7", size = 184720, upload-time = "2025-12-06T19:03:29.599Z" }, + { url = "https://files.pythonhosted.org/packages/99/bb/e4553433d7ac47f4c75d0a7e59b13aee0e08e88ceadbee356527a9629b0a/librt-0.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:572a24fc5958c61431da456a0ef1eeea6b4989d81eeb18b8e5f1f3077592200b", size = 180731, upload-time = "2025-12-06T19:03:31.201Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/51cd73006232981a3106d4081fbaa584ac4e27b49bc02266468d3919db03/librt-0.7.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6488e69d408b492e08bfb68f20c4a899a354b4386a446ecd490baff8d0862720", size = 174565, upload-time = "2025-12-06T19:03:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/42/54/0578a78b587e5aa22486af34239a052c6366835b55fc307bc64380229e3f/librt-0.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed028fc3d41adda916320712838aec289956c89b4f0a361ceadf83a53b4c047a", size = 195247, upload-time = "2025-12-06T19:03:34.434Z" }, + { url = "https://files.pythonhosted.org/packages/b5/0a/ee747cd999753dd9447e50b98fc36ee433b6c841a42dbf6d47b64b32a56e/librt-0.7.3-cp311-cp311-win32.whl", hash = "sha256:2cf9d73499486ce39eebbff5f42452518cc1f88d8b7ea4a711ab32962b176ee2", size = 47514, upload-time = "2025-12-06T19:03:35.959Z" }, + { url = "https://files.pythonhosted.org/packages/ec/af/8b13845178dec488e752878f8e290f8f89e7e34ae1528b70277aa1a6dd1e/librt-0.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:35f1609e3484a649bb80431310ddbec81114cd86648f1d9482bc72a3b86ded2e", size = 54695, upload-time = "2025-12-06T19:03:36.956Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/ae59578501b1a25850266778f59279f4f3e726acc5c44255bfcb07b4bc57/librt-0.7.3-cp311-cp311-win_arm64.whl", hash = "sha256:550fdbfbf5bba6a2960b27376ca76d6aaa2bd4b1a06c4255edd8520c306fcfc0", size = 48142, upload-time = "2025-12-06T19:03:38.263Z" }, + { url = "https://files.pythonhosted.org/packages/29/90/ed8595fa4e35b6020317b5ea8d226a782dcbac7a997c19ae89fb07a41c66/librt-0.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fa9ac2e49a6bee56e47573a6786cb635e128a7b12a0dc7851090037c0d397a3", size = 55687, upload-time = "2025-12-06T19:03:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f6/6a20702a07b41006cb001a759440cb6b5362530920978f64a2b2ae2bf729/librt-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e980cf1ed1a2420a6424e2ed884629cdead291686f1048810a817de07b5eb18", size = 57127, upload-time = "2025-12-06T19:03:40.3Z" }, + { url = "https://files.pythonhosted.org/packages/79/f3/b0c4703d5ffe9359b67bb2ccb86c42d4e930a363cfc72262ac3ba53cff3e/librt-0.7.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e094e445c37c57e9ec612847812c301840239d34ccc5d153a982fa9814478c60", size = 165336, upload-time = "2025-12-06T19:03:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/02/69/3ba05b73ab29ccbe003856232cea4049769be5942d799e628d1470ed1694/librt-0.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aca73d70c3f553552ba9133d4a09e767dcfeee352d8d8d3eb3f77e38a3beb3ed", size = 174237, upload-time = "2025-12-06T19:03:42.44Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/d7c2671e7bf6c285ef408aa435e9cd3fdc06fd994601e1f2b242df12034f/librt-0.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c634a0a6db395fdaba0361aa78395597ee72c3aad651b9a307a3a7eaf5efd67e", size = 189017, upload-time = "2025-12-06T19:03:44.01Z" }, + { url = "https://files.pythonhosted.org/packages/f4/94/d13f57193148004592b618555f296b41d2d79b1dc814ff8b3273a0bf1546/librt-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a59a69deeb458c858b8fea6acf9e2acd5d755d76cd81a655256bc65c20dfff5b", size = 183983, upload-time = "2025-12-06T19:03:45.834Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/b612a9944ebd39fa143c7e2e2d33f2cb790205e025ddd903fb509a3a3bb3/librt-0.7.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d91e60ac44bbe3a77a67af4a4c13114cbe9f6d540337ce22f2c9eaf7454ca71f", size = 177602, upload-time = "2025-12-06T19:03:46.944Z" }, + { url = "https://files.pythonhosted.org/packages/1f/48/77bc05c4cc232efae6c5592c0095034390992edbd5bae8d6cf1263bb7157/librt-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:703456146dc2bf430f7832fd1341adac5c893ec3c1430194fdcefba00012555c", size = 199282, upload-time = "2025-12-06T19:03:48.069Z" }, + { url = "https://files.pythonhosted.org/packages/12/aa/05916ccd864227db1ffec2a303ae34f385c6b22d4e7ce9f07054dbcf083c/librt-0.7.3-cp312-cp312-win32.whl", hash = "sha256:b7c1239b64b70be7759554ad1a86288220bbb04d68518b527783c4ad3fb4f80b", size = 47879, upload-time = "2025-12-06T19:03:49.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/92/7f41c42d31ea818b3c4b9cc1562e9714bac3c676dd18f6d5dd3d0f2aa179/librt-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef59c938f72bdbc6ab52dc50f81d0637fde0f194b02d636987cea2ab30f8f55a", size = 54972, upload-time = "2025-12-06T19:03:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/3f/dc/53582bbfb422311afcbc92adb75711f04e989cec052f08ec0152fbc36c9c/librt-0.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:ff21c554304e8226bf80c3a7754be27c6c3549a9fec563a03c06ee8f494da8fc", size = 48338, upload-time = "2025-12-06T19:03:51.431Z" }, + { url = "https://files.pythonhosted.org/packages/93/7d/e0ce1837dfb452427db556e6d4c5301ba3b22fe8de318379fbd0593759b9/librt-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56f2a47beda8409061bc1c865bef2d4bd9ff9255219402c0817e68ab5ad89aed", size = 55742, upload-time = "2025-12-06T19:03:52.459Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/3564262301e507e1d5cf31c7d84cb12addf0d35e05ba53312494a2eba9a4/librt-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14569ac5dd38cfccf0a14597a88038fb16811a6fede25c67b79c6d50fc2c8fdc", size = 57163, upload-time = "2025-12-06T19:03:53.516Z" }, + { url = "https://files.pythonhosted.org/packages/be/ac/245e72b7e443d24a562f6047563c7f59833384053073ef9410476f68505b/librt-0.7.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6038ccbd5968325a5d6fd393cf6e00b622a8de545f0994b89dd0f748dcf3e19e", size = 165840, upload-time = "2025-12-06T19:03:54.918Z" }, + { url = "https://files.pythonhosted.org/packages/98/af/587e4491f40adba066ba39a450c66bad794c8d92094f936a201bfc7c2b5f/librt-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d39079379a9a28e74f4d57dc6357fa310a1977b51ff12239d7271ec7e71d67f5", size = 174827, upload-time = "2025-12-06T19:03:56.082Z" }, + { url = "https://files.pythonhosted.org/packages/78/21/5b8c60ea208bc83dd00421022a3874330685d7e856404128dc3728d5d1af/librt-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8837d5a52a2d7aa9f4c3220a8484013aed1d8ad75240d9a75ede63709ef89055", size = 189612, upload-time = "2025-12-06T19:03:57.507Z" }, + { url = "https://files.pythonhosted.org/packages/da/2f/8b819169ef696421fb81cd04c6cdf225f6e96f197366001e9d45180d7e9e/librt-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:399bbd7bcc1633c3e356ae274a1deb8781c7bf84d9c7962cc1ae0c6e87837292", size = 184584, upload-time = "2025-12-06T19:03:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fc/af9d225a9395b77bd7678362cb055d0b8139c2018c37665de110ca388022/librt-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8d8cf653e798ee4c4e654062b633db36984a1572f68c3aa25e364a0ddfbbb910", size = 178269, upload-time = "2025-12-06T19:03:59.769Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d8/7b4fa1683b772966749d5683aa3fd605813defffe157833a8fa69cc89207/librt-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2f03484b54bf4ae80ab2e504a8d99d20d551bfe64a7ec91e218010b467d77093", size = 199852, upload-time = "2025-12-06T19:04:00.901Z" }, + { url = "https://files.pythonhosted.org/packages/77/e8/4598413aece46ca38d9260ef6c51534bd5f34b5c21474fcf210ce3a02123/librt-0.7.3-cp313-cp313-win32.whl", hash = "sha256:44b3689b040df57f492e02cd4f0bacd1b42c5400e4b8048160c9d5e866de8abe", size = 47936, upload-time = "2025-12-06T19:04:02.054Z" }, + { url = "https://files.pythonhosted.org/packages/af/80/ac0e92d5ef8c6791b3e2c62373863827a279265e0935acdf807901353b0e/librt-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:6b407c23f16ccc36614c136251d6b32bf30de7a57f8e782378f1107be008ddb0", size = 54965, upload-time = "2025-12-06T19:04:03.224Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/042f823fcbff25c1449bb4203a29919891ca74141b68d3a5f6612c4ce283/librt-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:abfc57cab3c53c4546aee31859ef06753bfc136c9d208129bad23e2eca39155a", size = 48350, upload-time = "2025-12-06T19:04:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ae/c6ecc7bb97134a71b5241e8855d39964c0e5f4d96558f0d60593892806d2/librt-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:120dd21d46ff875e849f1aae19346223cf15656be489242fe884036b23d39e93", size = 55175, upload-time = "2025-12-06T19:04:05.308Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bc/2cc0cb0ab787b39aa5c7645cd792433c875982bdf12dccca558b89624594/librt-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1617bea5ab31266e152871208502ee943cb349c224846928a1173c864261375e", size = 56881, upload-time = "2025-12-06T19:04:06.674Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/397417a386190b70f5bf26fcedbaa1515f19dce33366e2684c6b7ee83086/librt-0.7.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93b2a1f325fefa1482516ced160c8c7b4b8d53226763fa6c93d151fa25164207", size = 163710, upload-time = "2025-12-06T19:04:08.437Z" }, + { url = "https://files.pythonhosted.org/packages/c9/37/7338f85b80e8a17525d941211451199845093ca242b32efbf01df8531e72/librt-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d4801db8354436fd3936531e7f0e4feb411f62433a6b6cb32bb416e20b529f", size = 172471, upload-time = "2025-12-06T19:04:10.124Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e0/741704edabbfae2c852fedc1b40d9ed5a783c70ed3ed8e4fe98f84b25d13/librt-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11ad45122bbed42cfc8b0597450660126ef28fd2d9ae1a219bc5af8406f95678", size = 186804, upload-time = "2025-12-06T19:04:11.586Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d1/0a82129d6ba242f3be9af34815be089f35051bc79619f5c27d2c449ecef6/librt-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b4e7bff1d76dd2b46443078519dc75df1b5e01562345f0bb740cea5266d8218", size = 181817, upload-time = "2025-12-06T19:04:12.802Z" }, + { url = "https://files.pythonhosted.org/packages/4f/32/704f80bcf9979c68d4357c46f2af788fbf9d5edda9e7de5786ed2255e911/librt-0.7.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d86f94743a11873317094326456b23f8a5788bad9161fd2f0e52088c33564620", size = 175602, upload-time = "2025-12-06T19:04:14.004Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6d/4355cfa0fae0c062ba72f541d13db5bc575770125a7ad3d4f46f4109d305/librt-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:754a0d09997095ad764ccef050dd5bf26cbf457aab9effcba5890dad081d879e", size = 196497, upload-time = "2025-12-06T19:04:15.487Z" }, + { url = "https://files.pythonhosted.org/packages/2e/eb/ac6d8517d44209e5a712fde46f26d0055e3e8969f24d715f70bd36056230/librt-0.7.3-cp314-cp314-win32.whl", hash = "sha256:fbd7351d43b80d9c64c3cfcb50008f786cc82cba0450e8599fdd64f264320bd3", size = 44678, upload-time = "2025-12-06T19:04:16.688Z" }, + { url = "https://files.pythonhosted.org/packages/e9/93/238f026d141faf9958da588c761a0812a1a21c98cc54a76f3608454e4e59/librt-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:d376a35c6561e81d2590506804b428fc1075fcc6298fc5bb49b771534c0ba010", size = 51689, upload-time = "2025-12-06T19:04:17.726Z" }, + { url = "https://files.pythonhosted.org/packages/52/44/43f462ad9dcf9ed7d3172fe2e30d77b980956250bd90e9889a9cca93df2a/librt-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:cbdb3f337c88b43c3b49ca377731912c101178be91cb5071aac48faa898e6f8e", size = 44662, upload-time = "2025-12-06T19:04:18.771Z" }, + { url = "https://files.pythonhosted.org/packages/1d/35/fed6348915f96b7323241de97f26e2af481e95183b34991df12fd5ce31b1/librt-0.7.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9f0e0927efe87cd42ad600628e595a1a0aa1c64f6d0b55f7e6059079a428641a", size = 57347, upload-time = "2025-12-06T19:04:19.812Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f2/045383ccc83e3fea4fba1b761796584bc26817b6b2efb6b8a6731431d16f/librt-0.7.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:020c6db391268bcc8ce75105cb572df8cb659a43fd347366aaa407c366e5117a", size = 59223, upload-time = "2025-12-06T19:04:20.862Z" }, + { url = "https://files.pythonhosted.org/packages/77/3f/c081f8455ab1d7f4a10dbe58463ff97119272ff32494f21839c3b9029c2c/librt-0.7.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7af7785f5edd1f418da09a8cdb9ec84b0213e23d597413e06525340bcce1ea4f", size = 183861, upload-time = "2025-12-06T19:04:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f5/73c5093c22c31fbeaebc25168837f05ebfd8bf26ce00855ef97a5308f36f/librt-0.7.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ccadf260bb46a61b9c7e89e2218f6efea9f3eeaaab4e3d1f58571890e54858e", size = 194594, upload-time = "2025-12-06T19:04:23.14Z" }, + { url = "https://files.pythonhosted.org/packages/78/b8/d5f17d4afe16612a4a94abfded94c16c5a033f183074fb130dfe56fc1a42/librt-0.7.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9883b2d819ce83f87ba82a746c81d14ada78784db431e57cc9719179847376e", size = 206759, upload-time = "2025-12-06T19:04:24.328Z" }, + { url = "https://files.pythonhosted.org/packages/36/2e/021765c1be85ee23ffd5b5b968bb4cba7526a4db2a0fc27dcafbdfc32da7/librt-0.7.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:59cb0470612d21fa1efddfa0dd710756b50d9c7fb6c1236bbf8ef8529331dc70", size = 203210, upload-time = "2025-12-06T19:04:25.544Z" }, + { url = "https://files.pythonhosted.org/packages/77/f0/9923656e42da4fd18c594bd08cf6d7e152d4158f8b808e210d967f0dcceb/librt-0.7.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1fe603877e1865b5fd047a5e40379509a4a60204aa7aa0f72b16f7a41c3f0712", size = 196708, upload-time = "2025-12-06T19:04:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0b/0708b886ac760e64d6fbe7e16024e4be3ad1a3629d19489a97e9cf4c3431/librt-0.7.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5460d99ed30f043595bbdc888f542bad2caeb6226b01c33cda3ae444e8f82d42", size = 217212, upload-time = "2025-12-06T19:04:27.892Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7f/12a73ff17bca4351e73d585dd9ebf46723c4a8622c4af7fe11a2e2d011ff/librt-0.7.3-cp314-cp314t-win32.whl", hash = "sha256:d09f677693328503c9e492e33e9601464297c01f9ebd966ea8fc5308f3069bfd", size = 45586, upload-time = "2025-12-06T19:04:29.116Z" }, + { url = "https://files.pythonhosted.org/packages/e2/df/8decd032ac9b995e4f5606cde783711a71094128d88d97a52e397daf2c89/librt-0.7.3-cp314-cp314t-win_amd64.whl", hash = "sha256:25711f364c64cab2c910a0247e90b51421e45dbc8910ceeb4eac97a9e132fc6f", size = 53002, upload-time = "2025-12-06T19:04:30.173Z" }, + { url = "https://files.pythonhosted.org/packages/de/0c/6605b6199de8178afe7efc77ca1d8e6db00453bc1d3349d27605c0f42104/librt-0.7.3-cp314-cp314t-win_arm64.whl", hash = "sha256:a9f9b661f82693eb56beb0605156c7fca57f535704ab91837405913417d6990b", size = 45647, upload-time = "2025-12-06T19:04:31.302Z" }, ] [[package]] name = "markdown-it-py" -version = "3.0.0" +version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] [[package]] name = "matplotlib-inline" -version = "0.1.7" +version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, ] [[package]] @@ -585,42 +758,50 @@ wheels = [ [[package]] name = "more-itertools" -version = "10.7.0" +version = "10.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671, upload-time = "2025-04-22T14:17:41.838Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, ] [[package]] name = "mypy" -version = "1.15.0" +version = "1.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "librt" }, { name = "mypy-extensions" }, + { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload-time = "2025-02-05T03:50:34.655Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338, upload-time = "2025-02-05T03:50:17.287Z" }, - { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540, upload-time = "2025-02-05T03:49:51.21Z" }, - { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051, upload-time = "2025-02-05T03:50:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751, upload-time = "2025-02-05T03:49:42.408Z" }, - { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783, upload-time = "2025-02-05T03:49:07.707Z" }, - { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618, upload-time = "2025-02-05T03:49:54.581Z" }, - { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981, upload-time = "2025-02-05T03:50:28.25Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175, upload-time = "2025-02-05T03:50:13.411Z" }, - { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675, upload-time = "2025-02-05T03:50:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020, upload-time = "2025-02-05T03:48:48.705Z" }, - { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582, upload-time = "2025-02-05T03:49:03.628Z" }, - { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614, upload-time = "2025-02-05T03:50:00.313Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload-time = "2025-02-05T03:48:55.789Z" }, - { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload-time = "2025-02-05T03:48:44.581Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload-time = "2025-02-05T03:49:25.514Z" }, - { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload-time = "2025-02-05T03:49:57.623Z" }, - { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload-time = "2025-02-05T03:48:52.361Z" }, - { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload-time = "2025-02-05T03:49:11.395Z" }, - { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload-time = "2025-02-05T03:50:08.348Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/d2/010fb171ae5ac4a01cc34fbacd7544531e5ace95c35ca166dd8fd1b901d0/mypy-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a31e4c28e8ddb042c84c5e977e28a21195d086aaffaf08b016b78e19c9ef8106", size = 13010563, upload-time = "2025-11-28T15:48:23.975Z" }, + { url = "https://files.pythonhosted.org/packages/41/6b/63f095c9f1ce584fdeb595d663d49e0980c735a1d2004720ccec252c5d47/mypy-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34ec1ac66d31644f194b7c163d7f8b8434f1b49719d403a5d26c87fff7e913f7", size = 12077037, upload-time = "2025-11-28T15:47:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d7/83/6cb93d289038d809023ec20eb0b48bbb1d80af40511fa077da78af6ff7c7/mypy-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb64b0ba5980466a0f3f9990d1c582bcab8db12e29815ecb57f1408d99b4bff7", size = 12680255, upload-time = "2025-11-28T15:46:57.628Z" }, + { url = "https://files.pythonhosted.org/packages/99/db/d217815705987d2cbace2edd9100926196d6f85bcb9b5af05058d6e3c8ad/mypy-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:120cffe120cca5c23c03c77f84abc0c14c5d2e03736f6c312480020082f1994b", size = 13421472, upload-time = "2025-11-28T15:47:59.655Z" }, + { url = "https://files.pythonhosted.org/packages/4e/51/d2beaca7c497944b07594f3f8aad8d2f0e8fc53677059848ae5d6f4d193e/mypy-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a500ab5c444268a70565e374fc803972bfd1f09545b13418a5174e29883dab7", size = 13651823, upload-time = "2025-11-28T15:45:29.318Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d1/7883dcf7644db3b69490f37b51029e0870aac4a7ad34d09ceae709a3df44/mypy-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:c14a98bc63fd867530e8ec82f217dae29d0550c86e70debc9667fff1ec83284e", size = 10049077, upload-time = "2025-11-28T15:45:39.818Z" }, + { url = "https://files.pythonhosted.org/packages/11/7e/1afa8fb188b876abeaa14460dc4983f909aaacaa4bf5718c00b2c7e0b3d5/mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d", size = 13207728, upload-time = "2025-11-28T15:46:26.463Z" }, + { url = "https://files.pythonhosted.org/packages/b2/13/f103d04962bcbefb1644f5ccb235998b32c337d6c13145ea390b9da47f3e/mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760", size = 12202945, upload-time = "2025-11-28T15:48:49.143Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/a86a5608f74a22284a8ccea8592f6e270b61f95b8588951110ad797c2ddd/mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6", size = 12718673, upload-time = "2025-11-28T15:47:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/3d/58/cf08fff9ced0423b858f2a7495001fda28dc058136818ee9dffc31534ea9/mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2", size = 13608336, upload-time = "2025-11-28T15:48:32.625Z" }, + { url = "https://files.pythonhosted.org/packages/64/ed/9c509105c5a6d4b73bb08733102a3ea62c25bc02c51bca85e3134bf912d3/mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431", size = 13833174, upload-time = "2025-11-28T15:45:48.091Z" }, + { url = "https://files.pythonhosted.org/packages/cd/71/01939b66e35c6f8cb3e6fdf0b657f0fd24de2f8ba5e523625c8e72328208/mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018", size = 10112208, upload-time = "2025-11-28T15:46:41.702Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993, upload-time = "2025-11-28T15:47:22.336Z" }, + { url = "https://files.pythonhosted.org/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411, upload-time = "2025-11-28T15:44:55.492Z" }, + { url = "https://files.pythonhosted.org/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751, upload-time = "2025-11-28T15:44:14.169Z" }, + { url = "https://files.pythonhosted.org/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323, upload-time = "2025-11-28T15:46:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032, upload-time = "2025-11-28T15:46:18.286Z" }, + { url = "https://files.pythonhosted.org/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644, upload-time = "2025-11-28T15:47:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236, upload-time = "2025-11-28T15:45:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902, upload-time = "2025-11-28T15:46:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600, upload-time = "2025-11-28T15:44:22.521Z" }, + { url = "https://files.pythonhosted.org/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639, upload-time = "2025-11-28T15:48:08.55Z" }, + { url = "https://files.pythonhosted.org/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132, upload-time = "2025-11-28T15:47:06.032Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832, upload-time = "2025-11-28T15:47:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" }, ] [[package]] @@ -634,33 +815,47 @@ wheels = [ [[package]] name = "nh3" -version = "0.2.21" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/30/2f81466f250eb7f591d4d193930df661c8c23e9056bdc78e365b646054d8/nh3-0.2.21.tar.gz", hash = "sha256:4990e7ee6a55490dbf00d61a6f476c9a3258e31e711e13713b2ea7d6616f670e", size = 16581, upload-time = "2025-02-25T13:38:44.619Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/81/b83775687fcf00e08ade6d4605f0be9c4584cb44c4973d9f27b7456a31c9/nh3-0.2.21-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fcff321bd60c6c5c9cb4ddf2554e22772bb41ebd93ad88171bbbb6f271255286", size = 1297678, upload-time = "2025-02-25T13:37:56.063Z" }, - { url = "https://files.pythonhosted.org/packages/22/ee/d0ad8fb4b5769f073b2df6807f69a5e57ca9cea504b78809921aef460d20/nh3-0.2.21-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31eedcd7d08b0eae28ba47f43fd33a653b4cdb271d64f1aeda47001618348fde", size = 733774, upload-time = "2025-02-25T13:37:58.419Z" }, - { url = "https://files.pythonhosted.org/packages/ea/76/b450141e2d384ede43fe53953552f1c6741a499a8c20955ad049555cabc8/nh3-0.2.21-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d426d7be1a2f3d896950fe263332ed1662f6c78525b4520c8e9861f8d7f0d243", size = 760012, upload-time = "2025-02-25T13:38:01.017Z" }, - { url = "https://files.pythonhosted.org/packages/97/90/1182275db76cd8fbb1f6bf84c770107fafee0cb7da3e66e416bcb9633da2/nh3-0.2.21-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9d67709bc0d7d1f5797b21db26e7a8b3d15d21c9c5f58ccfe48b5328483b685b", size = 923619, upload-time = "2025-02-25T13:38:02.617Z" }, - { url = "https://files.pythonhosted.org/packages/29/c7/269a7cfbec9693fad8d767c34a755c25ccb8d048fc1dfc7a7d86bc99375c/nh3-0.2.21-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:55823c5ea1f6b267a4fad5de39bc0524d49a47783e1fe094bcf9c537a37df251", size = 1000384, upload-time = "2025-02-25T13:38:04.402Z" }, - { url = "https://files.pythonhosted.org/packages/68/a9/48479dbf5f49ad93f0badd73fbb48b3d769189f04c6c69b0df261978b009/nh3-0.2.21-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:818f2b6df3763e058efa9e69677b5a92f9bc0acff3295af5ed013da544250d5b", size = 918908, upload-time = "2025-02-25T13:38:06.693Z" }, - { url = "https://files.pythonhosted.org/packages/d7/da/0279c118f8be2dc306e56819880b19a1cf2379472e3b79fc8eab44e267e3/nh3-0.2.21-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b3b5c58161e08549904ac4abd450dacd94ff648916f7c376ae4b2c0652b98ff9", size = 909180, upload-time = "2025-02-25T13:38:10.941Z" }, - { url = "https://files.pythonhosted.org/packages/26/16/93309693f8abcb1088ae143a9c8dbcece9c8f7fb297d492d3918340c41f1/nh3-0.2.21-cp313-cp313t-win32.whl", hash = "sha256:637d4a10c834e1b7d9548592c7aad760611415fcd5bd346f77fd8a064309ae6d", size = 532747, upload-time = "2025-02-25T13:38:12.548Z" }, - { url = "https://files.pythonhosted.org/packages/a2/3a/96eb26c56cbb733c0b4a6a907fab8408ddf3ead5d1b065830a8f6a9c3557/nh3-0.2.21-cp313-cp313t-win_amd64.whl", hash = "sha256:713d16686596e556b65e7f8c58328c2df63f1a7abe1277d87625dcbbc012ef82", size = 528908, upload-time = "2025-02-25T13:38:14.059Z" }, - { url = "https://files.pythonhosted.org/packages/ba/1d/b1ef74121fe325a69601270f276021908392081f4953d50b03cbb38b395f/nh3-0.2.21-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a772dec5b7b7325780922dd904709f0f5f3a79fbf756de5291c01370f6df0967", size = 1316133, upload-time = "2025-02-25T13:38:16.601Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f2/2c7f79ce6de55b41e7715f7f59b159fd59f6cdb66223c05b42adaee2b645/nh3-0.2.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d002b648592bf3033adfd875a48f09b8ecc000abd7f6a8769ed86b6ccc70c759", size = 758328, upload-time = "2025-02-25T13:38:18.972Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ad/07bd706fcf2b7979c51b83d8b8def28f413b090cf0cb0035ee6b425e9de5/nh3-0.2.21-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2a5174551f95f2836f2ad6a8074560f261cf9740a48437d6151fd2d4d7d617ab", size = 747020, upload-time = "2025-02-25T13:38:20.571Z" }, - { url = "https://files.pythonhosted.org/packages/75/99/06a6ba0b8a0d79c3d35496f19accc58199a1fb2dce5e711a31be7e2c1426/nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b8d55ea1fc7ae3633d758a92aafa3505cd3cc5a6e40470c9164d54dff6f96d42", size = 944878, upload-time = "2025-02-25T13:38:22.204Z" }, - { url = "https://files.pythonhosted.org/packages/79/d4/dc76f5dc50018cdaf161d436449181557373869aacf38a826885192fc587/nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ae319f17cd8960d0612f0f0ddff5a90700fa71926ca800e9028e7851ce44a6f", size = 903460, upload-time = "2025-02-25T13:38:25.951Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c3/d4f8037b2ab02ebf5a2e8637bd54736ed3d0e6a2869e10341f8d9085f00e/nh3-0.2.21-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ca02ac6f27fc80f9894409eb61de2cb20ef0a23740c7e29f9ec827139fa578", size = 839369, upload-time = "2025-02-25T13:38:28.174Z" }, - { url = "https://files.pythonhosted.org/packages/11/a9/1cd3c6964ec51daed7b01ca4686a5c793581bf4492cbd7274b3f544c9abe/nh3-0.2.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5f77e62aed5c4acad635239ac1290404c7e940c81abe561fd2af011ff59f585", size = 739036, upload-time = "2025-02-25T13:38:30.539Z" }, - { url = "https://files.pythonhosted.org/packages/fd/04/bfb3ff08d17a8a96325010ae6c53ba41de6248e63cdb1b88ef6369a6cdfc/nh3-0.2.21-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:087ffadfdcd497658c3adc797258ce0f06be8a537786a7217649fc1c0c60c293", size = 768712, upload-time = "2025-02-25T13:38:32.992Z" }, - { url = "https://files.pythonhosted.org/packages/9e/aa/cfc0bf545d668b97d9adea4f8b4598667d2b21b725d83396c343ad12bba7/nh3-0.2.21-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ac7006c3abd097790e611fe4646ecb19a8d7f2184b882f6093293b8d9b887431", size = 930559, upload-time = "2025-02-25T13:38:35.204Z" }, - { url = "https://files.pythonhosted.org/packages/78/9d/6f5369a801d3a1b02e6a9a097d56bcc2f6ef98cffebf03c4bb3850d8e0f0/nh3-0.2.21-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6141caabe00bbddc869665b35fc56a478eb774a8c1dfd6fba9fe1dfdf29e6efa", size = 1008591, upload-time = "2025-02-25T13:38:37.099Z" }, - { url = "https://files.pythonhosted.org/packages/a6/df/01b05299f68c69e480edff608248313cbb5dbd7595c5e048abe8972a57f9/nh3-0.2.21-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:20979783526641c81d2f5bfa6ca5ccca3d1e4472474b162c6256745fbfe31cd1", size = 925670, upload-time = "2025-02-25T13:38:38.696Z" }, - { url = "https://files.pythonhosted.org/packages/3d/79/bdba276f58d15386a3387fe8d54e980fb47557c915f5448d8c6ac6f7ea9b/nh3-0.2.21-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a7ea28cd49293749d67e4fcf326c554c83ec912cd09cd94aa7ec3ab1921c8283", size = 917093, upload-time = "2025-02-25T13:38:40.249Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d8/c6f977a5cd4011c914fb58f5ae573b071d736187ccab31bfb1d539f4af9f/nh3-0.2.21-cp38-abi3-win32.whl", hash = "sha256:6c9c30b8b0d291a7c5ab0967ab200598ba33208f754f2f4920e9343bdd88f79a", size = 537623, upload-time = "2025-02-25T13:38:41.893Z" }, - { url = "https://files.pythonhosted.org/packages/23/fc/8ce756c032c70ae3dd1d48a3552577a325475af2a2f629604b44f571165c/nh3-0.2.21-cp38-abi3-win_amd64.whl", hash = "sha256:bb0014948f04d7976aabae43fcd4cb7f551f9f8ce785a4c9ef66e6c2590f8629", size = 535283, upload-time = "2025-02-25T13:38:43.355Z" }, +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/a5/34c26015d3a434409f4d2a1cd8821a06c05238703f49283ffeb937bef093/nh3-0.3.2.tar.gz", hash = "sha256:f394759a06df8b685a4ebfb1874fb67a9cbfd58c64fc5ed587a663c0e63ec376", size = 19288, upload-time = "2025-10-30T11:17:45.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/01/a1eda067c0ba823e5e2bb033864ae4854549e49fb6f3407d2da949106bfb/nh3-0.3.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d18957a90806d943d141cc5e4a0fefa1d77cf0d7a156878bf9a66eed52c9cc7d", size = 1419839, upload-time = "2025-10-30T11:17:09.956Z" }, + { url = "https://files.pythonhosted.org/packages/30/57/07826ff65d59e7e9cc789ef1dc405f660cabd7458a1864ab58aefa17411b/nh3-0.3.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45c953e57028c31d473d6b648552d9cab1efe20a42ad139d78e11d8f42a36130", size = 791183, upload-time = "2025-10-30T11:17:11.99Z" }, + { url = "https://files.pythonhosted.org/packages/af/2f/e8a86f861ad83f3bb5455f596d5c802e34fcdb8c53a489083a70fd301333/nh3-0.3.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c9850041b77a9147d6bbd6dbbf13eeec7009eb60b44e83f07fcb2910075bf9b", size = 829127, upload-time = "2025-10-30T11:17:13.192Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/77aef4daf0479754e8e90c7f8f48f3b7b8725a3b8c0df45f2258017a6895/nh3-0.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:403c11563e50b915d0efdb622866d1d9e4506bce590ef7da57789bf71dd148b5", size = 997131, upload-time = "2025-10-30T11:17:14.677Z" }, + { url = "https://files.pythonhosted.org/packages/41/ee/fd8140e4df9d52143e89951dd0d797f5546004c6043285289fbbe3112293/nh3-0.3.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:0dca4365db62b2d71ff1620ee4f800c4729849906c5dd504ee1a7b2389558e31", size = 1068783, upload-time = "2025-10-30T11:17:15.861Z" }, + { url = "https://files.pythonhosted.org/packages/87/64/bdd9631779e2d588b08391f7555828f352e7f6427889daf2fa424bfc90c9/nh3-0.3.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0fe7ee035dd7b2290715baf29cb27167dddd2ff70ea7d052c958dbd80d323c99", size = 994732, upload-time = "2025-10-30T11:17:17.155Z" }, + { url = "https://files.pythonhosted.org/packages/79/66/90190033654f1f28ca98e3d76b8be1194505583f9426b0dcde782a3970a2/nh3-0.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a40202fd58e49129764f025bbaae77028e420f1d5b3c8e6f6fd3a6490d513868", size = 975997, upload-time = "2025-10-30T11:17:18.77Z" }, + { url = "https://files.pythonhosted.org/packages/34/30/ebf8e2e8d71fdb5a5d5d8836207177aed1682df819cbde7f42f16898946c/nh3-0.3.2-cp314-cp314t-win32.whl", hash = "sha256:1f9ba555a797dbdcd844b89523f29cdc90973d8bd2e836ea6b962cf567cadd93", size = 583364, upload-time = "2025-10-30T11:17:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/94/ae/95c52b5a75da429f11ca8902c2128f64daafdc77758d370e4cc310ecda55/nh3-0.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:dce4248edc427c9b79261f3e6e2b3ecbdd9b88c267012168b4a7b3fc6fd41d13", size = 589982, upload-time = "2025-10-30T11:17:21.384Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bd/c7d862a4381b95f2469704de32c0ad419def0f4a84b7a138a79532238114/nh3-0.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:019ecbd007536b67fdf76fab411b648fb64e2257ca3262ec80c3425c24028c80", size = 577126, upload-time = "2025-10-30T11:17:22.755Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3e/f5a5cc2885c24be13e9b937441bd16a012ac34a657fe05e58927e8af8b7a/nh3-0.3.2-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7064ccf5ace75825bd7bf57859daaaf16ed28660c1c6b306b649a9eda4b54b1e", size = 1431980, upload-time = "2025-10-30T11:17:25.457Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f7/529a99324d7ef055de88b690858f4189379708abae92ace799365a797b7f/nh3-0.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8745454cdd28bbbc90861b80a0111a195b0e3961b9fa2e672be89eb199fa5d8", size = 820805, upload-time = "2025-10-30T11:17:26.98Z" }, + { url = "https://files.pythonhosted.org/packages/3d/62/19b7c50ccd1fa7d0764822d2cea8f2a320f2fd77474c7a1805cb22cf69b0/nh3-0.3.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72d67c25a84579f4a432c065e8b4274e53b7cf1df8f792cf846abfe2c3090866", size = 803527, upload-time = "2025-10-30T11:17:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ca/f022273bab5440abff6302731a49410c5ef66b1a9502ba3fbb2df998d9ff/nh3-0.3.2-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:13398e676a14d6233f372c75f52d5ae74f98210172991f7a3142a736bd92b131", size = 1051674, upload-time = "2025-10-30T11:17:29.909Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f7/5728e3b32a11daf5bd21cf71d91c463f74305938bc3eb9e0ac1ce141646e/nh3-0.3.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03d617e5c8aa7331bd2659c654e021caf9bba704b109e7b2b28b039a00949fe5", size = 1004737, upload-time = "2025-10-30T11:17:31.205Z" }, + { url = "https://files.pythonhosted.org/packages/53/7f/f17e0dba0a99cee29e6cee6d4d52340ef9cb1f8a06946d3a01eb7ec2fb01/nh3-0.3.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f55c4d2d5a207e74eefe4d828067bbb01300e06e2a7436142f915c5928de07", size = 911745, upload-time = "2025-10-30T11:17:32.945Z" }, + { url = "https://files.pythonhosted.org/packages/42/0f/c76bf3dba22c73c38e9b1113b017cf163f7696f50e003404ec5ecdb1e8a6/nh3-0.3.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb18403f02b655a1bbe4e3a4696c2ae1d6ae8f5991f7cacb684b1ae27e6c9f7", size = 797184, upload-time = "2025-10-30T11:17:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/08/a1/73d8250f888fb0ddf1b119b139c382f8903d8bb0c5bd1f64afc7e38dad1d/nh3-0.3.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d66f41672eb4060cf87c037f760bdbc6847852ca9ef8e9c5a5da18f090abf87", size = 838556, upload-time = "2025-10-30T11:17:35.875Z" }, + { url = "https://files.pythonhosted.org/packages/d1/09/deb57f1fb656a7a5192497f4a287b0ade5a2ff6b5d5de4736d13ef6d2c1f/nh3-0.3.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f97f8b25cb2681d25e2338148159447e4d689aafdccfcf19e61ff7db3905768a", size = 1006695, upload-time = "2025-10-30T11:17:37.071Z" }, + { url = "https://files.pythonhosted.org/packages/b6/61/8f4d41c4ccdac30e4b1a4fa7be4b0f9914d8314a5058472f84c8e101a418/nh3-0.3.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:2ab70e8c6c7d2ce953d2a58102eefa90c2d0a5ed7aa40c7e29a487bc5e613131", size = 1075471, upload-time = "2025-10-30T11:17:38.225Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c6/966aec0cb4705e69f6c3580422c239205d5d4d0e50fac380b21e87b6cf1b/nh3-0.3.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:1710f3901cd6440ca92494ba2eb6dc260f829fa8d9196b659fa10de825610ce0", size = 1002439, upload-time = "2025-10-30T11:17:39.553Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c8/97a2d5f7a314cce2c5c49f30c6f161b7f3617960ade4bfc2fd1ee092cb20/nh3-0.3.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91e9b001101fb4500a2aafe3e7c92928d85242d38bf5ac0aba0b7480da0a4cd6", size = 987439, upload-time = "2025-10-30T11:17:40.81Z" }, + { url = "https://files.pythonhosted.org/packages/0d/95/2d6fc6461687d7a171f087995247dec33e8749a562bfadd85fb5dbf37a11/nh3-0.3.2-cp38-abi3-win32.whl", hash = "sha256:169db03df90da63286e0560ea0efa9b6f3b59844a9735514a1d47e6bb2c8c61b", size = 589826, upload-time = "2025-10-30T11:17:42.239Z" }, + { url = "https://files.pythonhosted.org/packages/64/9a/1a1c154f10a575d20dd634e5697805e589bbdb7673a0ad00e8da90044ba7/nh3-0.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:562da3dca7a17f9077593214a9781a94b8d76de4f158f8c895e62f09573945fe", size = 596406, upload-time = "2025-10-30T11:17:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7e/a96255f63b7aef032cbee8fc4d6e37def72e3aaedc1f72759235e8f13cb1/nh3-0.3.2-cp38-abi3-win_arm64.whl", hash = "sha256:cf5964d54edd405e68583114a7cba929468bcd7db5e676ae38ee954de1cfc104", size = 584162, upload-time = "2025-10-30T11:17:44.96Z" }, +] + +[[package]] +name = "outcome" +version = "1.3.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", size = 21060, upload-time = "2023-10-26T04:26:04.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692, upload-time = "2023-10-26T04:26:02.532Z" }, ] [[package]] @@ -674,11 +869,20 @@ wheels = [ [[package]] name = "parso" -version = "0.8.4" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609, upload-time = "2024-04-05T09:43:55.897Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650, upload-time = "2024-04-05T09:43:53.299Z" }, + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] @@ -722,23 +926,23 @@ wheels = [ [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "prompt-toolkit" -version = "3.0.51" +version = "3.0.52" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] [[package]] @@ -770,20 +974,29 @@ wheels = [ [[package]] name = "pycparser" -version = "2.22" +version = "2.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, ] [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, ] [[package]] @@ -851,7 +1064,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.3" +version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -859,9 +1072,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] @@ -887,15 +1100,15 @@ wheels = [ [[package]] name = "rich" -version = "14.0.0" +version = "14.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, ] [[package]] @@ -925,24 +1138,41 @@ wheels = [ [[package]] name = "secretstorage" -version = "3.3.3" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "jeepney" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739, upload-time = "2022-08-13T16:22:46.976Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221, upload-time = "2022-08-13T16:22:44.457Z" }, + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + +[[package]] +name = "selenium" +version = "4.39.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "trio" }, + { name = "trio-websocket" }, + { name = "typing-extensions" }, + { name = "urllib3", extra = ["socks"] }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/19/27c1bf9eb1f7025632d35a956b50746efb4b10aa87f961b263fa7081f4c5/selenium-4.39.0.tar.gz", hash = "sha256:12f3325f02d43b6c24030fc9602b34a3c6865abbb1db9406641d13d108aa1889", size = 928575, upload-time = "2025-12-06T23:12:34.896Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/d0/55a6b7c6f35aad4c8a54be0eb7a52c1ff29a59542fc3e655f0ecbb14456d/selenium-4.39.0-py3-none-any.whl", hash = "sha256:c85f65d5610642ca0f47dae9d5cc117cd9e831f74038bc09fe1af126288200f9", size = 9655249, upload-time = "2025-12-06T23:12:33.085Z" }, ] [[package]] name = "setuptools" -version = "80.4.0" +version = "80.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/0cc40fe41fd2adb80a2f388987f4f8db3c866c69e33e0b4c8b093fdf700e/setuptools-80.4.0.tar.gz", hash = "sha256:5a78f61820bc088c8e4add52932ae6b8cf423da2aff268c23f813cfbb13b4006", size = 1315008, upload-time = "2025-05-09T20:42:27.972Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/93/dba5ed08c2e31ec7cdc2ce75705a484ef0be1a2fecac8a58272489349de8/setuptools-80.4.0-py3-none-any.whl", hash = "sha256:6cdc8cb9a7d590b237dbe4493614a9b75d0559b888047c1f67d49ba50fc3edb2", size = 1200812, upload-time = "2025-05-09T20:42:25.325Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] [[package]] @@ -954,6 +1184,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + [[package]] name = "stack-data" version = "0.6.3" @@ -988,41 +1236,51 @@ wheels = [ [[package]] name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, ] [[package]] @@ -1034,6 +1292,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, ] +[[package]] +name = "trio" +version = "0.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cffi", marker = "implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "idna" }, + { name = "outcome" }, + { name = "sniffio" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/ce/0041ddd9160aac0031bcf5ab786c7640d795c797e67c438e15cfedf815c8/trio-0.32.0.tar.gz", hash = "sha256:150f29ec923bcd51231e1d4c71c7006e65247d68759dd1c19af4ea815a25806b", size = 605323, upload-time = "2025-10-31T07:18:17.466Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/bf/945d527ff706233636c73880b22c7c953f3faeb9d6c7e2e85bfbfd0134a0/trio-0.32.0-py3-none-any.whl", hash = "sha256:4ab65984ef8370b79a76659ec87aa3a30c5c7c83ff250b4de88c29a8ab6123c5", size = 512030, upload-time = "2025-10-31T07:18:15.885Z" }, +] + +[[package]] +name = "trio-websocket" +version = "0.12.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "outcome" }, + { name = "trio" }, + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/3c/8b4358e81f2f2cfe71b66a267f023a91db20a817b9425dd964873796980a/trio_websocket-0.12.2.tar.gz", hash = "sha256:22c72c436f3d1e264d0910a3951934798dcc5b00ae56fc4ee079d46c7cf20fae", size = 33549, upload-time = "2025-02-25T05:16:58.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/19/eb640a397bba49ba49ef9dbe2e7e5c04202ba045b6ce2ec36e9cadc51e04/trio_websocket-0.12.2-py3-none-any.whl", hash = "sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6", size = 21221, upload-time = "2025-02-25T05:16:57.545Z" }, +] + [[package]] name = "twine" version = "5.1.1" @@ -1089,20 +1378,25 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.13.2" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "urllib3" -version = "2.4.0" +version = "2.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/1d/0f3a93cca1ac5e8287842ed4eebbd0f7a991315089b1a0b01c7788aa7b63/urllib3-2.6.1.tar.gz", hash = "sha256:5379eb6e1aba4088bae84f8242960017ec8d8e3decf30480b3a1abdaa9671a3f", size = 432678, upload-time = "2025-12-08T15:25:26.773Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, + { url = "https://files.pythonhosted.org/packages/bc/56/190ceb8cb10511b730b564fb1e0293fa468363dbad26145c34928a60cb0c/urllib3-2.6.1-py3-none-any.whl", hash = "sha256:e67d06fe947c36a7ca39f4994b08d73922d40e6cca949907be05efa6fd75110b", size = 131138, upload-time = "2025-12-08T15:25:25.51Z" }, +] + +[package.optional-dependencies] +socks = [ + { name = "pysocks" }, ] [[package]] @@ -1119,11 +1413,20 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.2.13" +version = "0.2.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, ] [[package]] @@ -1135,11 +1438,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/d6/003e593296a85fd6ed616ed962795b2f87709c3eee2bca4f6d0fe55c6d00/wheel-0.37.1-py2.py3-none-any.whl", hash = "sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a", size = 35301, upload-time = "2021-12-22T12:22:00.742Z" }, ] +[[package]] +name = "wsproto" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/79/12135bdf8b9c9367b8701c2c19a14c913c120b882d50b014ca0d38083c2c/wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294", size = 50116, upload-time = "2025-11-20T18:18:01.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" }, +] + [[package]] name = "zipp" -version = "3.21.0" +version = "3.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545, upload-time = "2024-11-10T15:05:20.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630, upload-time = "2024-11-10T15:05:19.275Z" }, + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ] From 2312f6404eb6298646b1f5559a6c3c6b9d12ddaf Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 02:20:51 -0500 Subject: [PATCH 014/108] Update CLI and assc. tests --- src/fpds/cli/__init__.py | 32 ++--------- src/fpds/cli/fields.py | 93 +++++++++++++++++--------------- src/fpds/cli/parse.py | 113 ++++++++++++--------------------------- src/fpds/cli/root.py | 7 +++ tests/test_cli.py | 31 ++++++++--- 5 files changed, 118 insertions(+), 158 deletions(-) create mode 100644 src/fpds/cli/root.py diff --git a/src/fpds/cli/__init__.py b/src/fpds/cli/__init__.py index 986f0e4f..a3aa354f 100644 --- a/src/fpds/cli/__init__.py +++ b/src/fpds/cli/__init__.py @@ -1,30 +1,4 @@ -"""CLI namespace.""" +from fpds.cli.root import app -import click - -from .parse import parse as _parse -from .fields import fields as _fields - - -@click.group(invoke_without_command=True) -@click.pass_context -def cli(ctx): - """ - CLI for parsing the FPDS ATOM feed found at - https://www.fpds.gov/fpdsng_cms/index.php/en/ - """ - ascii_art = r""" - __________ ____ _____ - / ____/ __ \/ __ \/ ___/ - / /_ / /_/ / / / /\__ \ - / __/ / ____/ /_/ /___/ / - /_/ /_/ /_____//____/ - """ - click.echo(ascii_art + "\nWelcome to a more user-friendly FPDS 🚀\n") - - if ctx.invoked_subcommand is None: - click.echo(ctx.get_help()) - - -cli.add_command(_parse) -cli.add_command(_fields) +import fpds.cli.fields +import fpds.cli.parse diff --git a/src/fpds/cli/fields.py b/src/fpds/cli/fields.py index bbf6c7f0..76502fc1 100644 --- a/src/fpds/cli/fields.py +++ b/src/fpds/cli/fields.py @@ -1,55 +1,64 @@ -""" -Command for displaying FPDS filtering fields. -author: derek663@gmail.com -last_updated: 2025-10-18 -""" - -import click -import json +import re import textwrap -from fpds.config import FPDS_DATA_DIR, FPDS_FIELDS_CONFIG +import typer from tabulate import tabulate +from typing_extensions import Annotated +from fpds.config import FPDS_FIELDS_CONFIG +from fpds.cli.root import app + +TEXT_WRAP_WIDTH = 30 -@click.option( - "-e", - "--export", - required=False, - type=bool, - help="If True, exports full list of field metadata", -) -@click.command() -def fields(export): - """ - Command for displaying available filtering fields for the parsing command. + +@app.command() +def fields( + pattern: Annotated[ + str, + typer.Option( + "--pattern", + "-p", + help=( + "String pattern to scan field name with. Will scan string and " + "attempt to find a match at the first location. Case-insensitive." + ), + ), + ] = None, + width: Annotated[ + int, typer.Option("--width", "-w", help="Text wrap width for regex field.") + ] = TEXT_WRAP_WIDTH, +): + """Displays list of available FPDS fields and their descriptions. \b Usage: - $ fpds fields [OPTIONS] + $ uv run fpds fields [OPTIONS] \b - Options: - -e, --export If True, exports all available metadata for fields. + Example(s): + $ uv run fpds fields -p vendor + """ - parameters = FPDS_FIELDS_CONFIG - - if export: - metadata_file = FPDS_DATA_DIR / "fields.json" - with open(metadata_file, "w") as f: - json.dump(parameters, f) - click.echo(click.style(f"Fields metadata exported to {metadata_file}", fg="green")) - return - - message = ( - f"The following {len(parameters)} fields support regex validation. " - "If you wish to see more details about fields, set the --export flag to True. " - "If your field is not listed or if the regex pattern is no longer valid, " - "bypass validation by using the --skip-regex-validation flag. " - "Consult the README.md for examples." + + data = [] + prog = re.compile(pattern, flags=re.I) if pattern else None + + def text_wrap(text: str, width: int = width) -> str: + return textwrap.fill(text=text, width=width) + + for field in FPDS_FIELDS_CONFIG: + if prog and not prog.search(field["name"]): + continue + + data.append( + [ + field["name"], + text_wrap(field["description"], width=20), + text_wrap(field["regex"], width=width), + ] + ) + + print( + tabulate(data, headers=["Name", "Description", "Regex"], tablefmt="fancy_grid") ) - click.echo(click.style("\n".join(textwrap.wrap(message, width=87)), fg="green")) - data = [[field['name'], field['description']] for field in FPDS_FIELDS_CONFIG] - headers = ["Name", "Description"] - click.echo(tabulate(data, headers=headers, tablefmt="fancy_grid")) diff --git a/src/fpds/cli/parse.py b/src/fpds/cli/parse.py index 77018712..7c8dde7e 100644 --- a/src/fpds/cli/parse.py +++ b/src/fpds/cli/parse.py @@ -1,91 +1,46 @@ -""" -Parsing command for retrieving FPDS federal -contracts. - -author: derek663@gmail.com -last_updated: 2025-07-14 -""" - import asyncio -import json from pathlib import Path -from uuid import uuid4 +from typing import Optional -import click -from click import UsageError +import typer +from typing_extensions import Annotated from fpds import fpdsRequest +from fpds.cli.root import app from fpds.config import FPDS_DATA_DATE_DIR from fpds.utilities import validate_kwarg - -@click.command() -@click.option( - "-k", - "--skip-regex-validation", - metavar="", - required=False, - default=False, - type=bool, - help="If True, skips param regex validation", -) -@click.option( - "-o", - "--output-dir", - required=False, - metavar="", - type=click.Path(exists=False, path_type=Path), - help="Output directory", -) -@click.argument("params", nargs=-1) -def parse(params, output_dir, skip_regex_validation) -> None: # type: ignore - """ - Parsing command for the FPDS Atom feed - - \b - Usage: - $ fpds parse [PARAMS] [OPTIONS] - - \b - Positional Argument(s): - PARAMS Search criteria parameters for filtering response - - \b - Reference the Atom Feed Usage documentation at - https://www.fpds.gov/wiki/index.php/Atom_Feed_Usage - to determine available parameters. As an example, if - a user wants to filter for AWARD contract types, the - parameter criteria should look like this: 'CONTRACT_TYPE=AWARD'. - A full CLI command could look like this: - - \b - fpds parse "LAST_MOD_DATE=[2022/01/01, 2022/05/01]" "AGENCY_CODE=7504" - """ - - if output_dir: - if not output_dir.exists(): - click.echo(f"Creating output directory {str(output_dir.resolve())}") - output_dir.mkdir(parents=True, exist_ok=True) - - params = [param.split("=") for param in params] - - if not params: - raise UsageError("Please provide at least one parameter") - - for _param in params: # _param is a tuple +from fpds.cli import app + +@app.command() +def parse( + output_dir: Annotated[ + Optional[Path], + typer.Option( + "--output-dir", + "-o", + file_okay=False, + dir_okay=True, + writable=True, + help="Directory to output extracted FPDS data to.", + ), + ] = FPDS_DATA_DATE_DIR, + params: list[str] = typer.Argument( + ..., + help="Positional parameters (variadic, like nargs=-1)", + ), +): + """Sends ATOM feed request to FPDS.""" + if not output_dir.exists(): + output_dir.mkdir(parents=True, exist_ok=True) + + split_params = [param.split("=") for param in params] + + for _param in split_params: # _param is a tuple name, value = _param _param[1] = validate_kwarg(kwarg=name, string=value) - params_kwargs = dict(params) - click.echo(f"Params to be used for FPDS search: {params_kwargs}") - - request = fpdsRequest(**params_kwargs, cli_run=True, skip_regex_validation=skip_regex_validation) - click.echo("Retrieving FPDS records from ATOM feed...") - - records = asyncio.run(request.data()) - DATA_DIR = output_dir if output_dir else FPDS_DATA_DATE_DIR - DATA_FILE = DATA_DIR / f"{uuid4()}.json" - with open(DATA_FILE, "w") as outfile: - json.dump(records, outfile) + params_kwargs = dict(split_params) - click.echo(f"{len(records)} record(s) have been saved as JSON at: {DATA_FILE}") + request = fpdsRequest(**params_kwargs, cli_run=True) + asyncio.run(request.data(output_dir=str(output_dir))) diff --git a/src/fpds/cli/root.py b/src/fpds/cli/root.py new file mode 100644 index 00000000..b4f109e8 --- /dev/null +++ b/src/fpds/cli/root.py @@ -0,0 +1,7 @@ +import typer + +app = typer.Typer() + +@app.callback() +def callback(): + """Welcome to the fpds CLI 🚀""" diff --git a/tests/test_cli.py b/tests/test_cli.py index 84f749ea..78e0f742 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,27 +1,42 @@ import unittest from unittest import TestCase -from click.testing import CliRunner +from typer.testing import CliRunner -from fpds.cli import cli +from fpds.cli import app class TestFpdsCLI(TestCase): def setUp(self): self.runner = CliRunner() - def test_missing_cli_parameters(self): - result = self.runner.invoke(cli, ["parse"]) - self.assertIn("Please provide at least one parameter", result.output) + def test_assert_required_params_with_parse_command(self): + result = self.runner.invoke(app, ["parse"]) + print(result.output) + self.assertIn("Missing argument", result.output) def test_invalid_cli_parameter(self): - result = self.runner.invoke(cli, ["parse", "{an-invalid-param}={some-value}"]) - self.assertIn("is not a valid FPDS parameter", result.__str__()) + result = self.runner.invoke(app, ["parse", "FAKE_PARAM=FAKE_VALUE"]) + self.assertEqual(result.exit_code, 1) + self.assertIn("not a valid FPDS parameter", result.__str__()) def test_invalid_cli_parameter_regex_pattern(self): - result = self.runner.invoke(cli, ["parse", "AGENCY_CODE={not-valid}"]) + result = self.runner.invoke(app, ["parse", "AGENCY_CODE={not-valid}"]) self.assertIn("does not match regex", result.__str__()) + def test_parse_with_custom_output_dir(self): + with self.runner.isolated_filesystem(): + result = self.runner.invoke( + app, + [ + "parse", + "AGENCY_CODE=7504", + "-o", + "./test", + ] + ) + self.assertEqual(result.exit_code, 0) + if __name__ == "__main__": unittest.main() From 4c0ef8809246f61322b20f8138250965511b3409 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 02:21:26 -0500 Subject: [PATCH 015/108] Add ezsearch url to config --- src/fpds/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fpds/config.py b/src/fpds/config.py index 24659a03..45d1b978 100644 --- a/src/fpds/config.py +++ b/src/fpds/config.py @@ -12,6 +12,7 @@ HOME = Path.home() CURRENT_DATE = datetime.now().strftime("%Y-%m-%d") +FPDS_EZSEARCH_URL = "https://www.fpds.gov/ezsearch/fpdsportal?s=FPDS.GOV&templateName=1.5.3&indexName=awardfull&q=" # FPDS-specific configurations FPDS_DATA_DIR = HOME / ".fpds" From d31e1fca19d41e1d7b04ed97d7af91aed7e31c89 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 02:27:34 -0500 Subject: [PATCH 016/108] Add docstrings to CLI files --- src/fpds/cli/fields.py | 5 +++++ src/fpds/cli/parse.py | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/fpds/cli/fields.py b/src/fpds/cli/fields.py index 76502fc1..e99649e2 100644 --- a/src/fpds/cli/fields.py +++ b/src/fpds/cli/fields.py @@ -1,3 +1,8 @@ +"""CLI command for listing available fitering fields. + +author: derek663@gmail.com +last_updated: 2025-12-14 +""" import re import textwrap diff --git a/src/fpds/cli/parse.py b/src/fpds/cli/parse.py index 7c8dde7e..db2ab17e 100644 --- a/src/fpds/cli/parse.py +++ b/src/fpds/cli/parse.py @@ -1,3 +1,10 @@ +""" +CLI command for retrieving FPDS federal contracts. + +author: derek663@gmail.com +last_updated: 2025-12-14 +""" + import asyncio from pathlib import Path from typing import Optional From 5b1160fc6f75685c1aaeaf4ca617d4ec842026aa Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 02:28:45 -0500 Subject: [PATCH 017/108] Update dependencies --- pyproject.toml | 4 +--- uv.lock | 9 +++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5aabf7d7..862eb1f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ classifiers = [ ] dependencies = [ "httpx==0.28.1", + "tabulate==0.9.0", "typer==0.19.2", "typing-extensions>=4.15.0", ] @@ -25,9 +26,6 @@ Repository = "https://github.com/dherincx92/fpds" Issues = "https://github.com/dherincx92/fpds/issues" [project.optional-dependencies] -cli = [ - "tabulate==0.9.0", -] scripts = [ "selenium>=4.0.0,<5.0.0", ] diff --git a/uv.lock b/uv.lock index b901b385..f5f1dbd4 100644 --- a/uv.lock +++ b/uv.lock @@ -409,6 +409,7 @@ name = "fpds" source = { editable = "." } dependencies = [ { name = "httpx" }, + { name = "tabulate" }, { name = "typer" }, { name = "typing-extensions" }, ] @@ -423,16 +424,12 @@ all = [ { name = "pytest-cov" }, { name = "pytest-runner" }, { name = "ruff" }, - { name = "tabulate" }, { name = "twine" }, { name = "types-tabulate" }, { name = "types-tqdm" }, { name = "versioningit" }, { name = "wheel" }, ] -cli = [ - { name = "tabulate" }, -] dev = [ { name = "ipdb" }, { name = "ipython" }, @@ -472,7 +469,7 @@ requires-dist = [ { name = "pytest-runner", marker = "extra == 'tests'", specifier = "==6.0.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = "==0.12.1" }, { name = "selenium", marker = "extra == 'scripts'", specifier = ">=4.0.0,<5.0.0" }, - { name = "tabulate", marker = "extra == 'cli'", specifier = "==0.9.0" }, + { name = "tabulate", specifier = "==0.9.0" }, { name = "twine", marker = "extra == 'packaging'", specifier = "==5.1.1" }, { name = "typer", specifier = "==0.19.2" }, { name = "types-tabulate", marker = "extra == 'dev'", specifier = "==0.9.0.20241207" }, @@ -481,7 +478,7 @@ requires-dist = [ { name = "versioningit", marker = "extra == 'dev'", specifier = ">=3.0.0,<4.0.0" }, { name = "wheel", marker = "extra == 'packaging'", specifier = "==0.37.1" }, ] -provides-extras = ["cli", "scripts", "dev", "tests", "packaging", "all"] +provides-extras = ["scripts", "dev", "tests", "packaging", "all"] [[package]] name = "h11" From 39dad34341aaa9ffc7eceacb70720ddeb773a048 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 02:29:17 -0500 Subject: [PATCH 018/108] add headless option to selenium scraper --- src/fpds/scripts/scraper.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 0d5f588f..ac2115fd 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -35,11 +35,19 @@ def update_fields_json(dropdown_fields): config.extend(new_options) sorted_config = sorted(config, key=lambda field: field["name"]) - with Path(FPDS_FIELDS_FILE_PATH).open(mode="w", encoding="utf-8") as file: - json.dump(sorted_config, file, indent=4) + print(new_options) + # with Path(FPDS_FIELDS_FILE_PATH).open(mode="w", encoding="utf-8") as file: + # json.dump(sorted_config, file, indent=4) def scrape_ezsearch(): - driver = webdriver.Chrome() + """Scrapes FPDS ezSearch field for Advanced Search Criteria dropdown values. + + These values represent valid parameter values for an instance of + `:class:`fpdsRequest. + """ + options = Options() + options.add_argument("--headless") + driver = webdriver.Chrome(options=options) driver.get(FPDS_EZSEARCH_URL) search_criteria_button = driver.find_element( From f815fc6f471232c27987849b9b840ef5faa11597 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 11:05:47 -0500 Subject: [PATCH 019/108] Install scripts extra in pyproject.toml --- pyproject.toml | 1 + uv.lock | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 862eb1f7..e32e422c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ packaging = [ all = [ "fpds[cli]", "fpds[dev]", + "fpds[scripts]", "fpds[tests]", "fpds[packaging]", ] diff --git a/uv.lock b/uv.lock index f5f1dbd4..f635d588 100644 --- a/uv.lock +++ b/uv.lock @@ -424,6 +424,7 @@ all = [ { name = "pytest-cov" }, { name = "pytest-runner" }, { name = "ruff" }, + { name = "selenium" }, { name = "twine" }, { name = "types-tabulate" }, { name = "types-tqdm" }, @@ -459,6 +460,7 @@ requires-dist = [ { name = "fpds", extras = ["cli"], marker = "extra == 'all'" }, { name = "fpds", extras = ["dev"], marker = "extra == 'all'" }, { name = "fpds", extras = ["packaging"], marker = "extra == 'all'" }, + { name = "fpds", extras = ["scripts"], marker = "extra == 'all'" }, { name = "fpds", extras = ["tests"], marker = "extra == 'all'" }, { name = "httpx", specifier = "==0.28.1" }, { name = "ipdb", marker = "extra == 'dev'", specifier = "==0.13.9" }, From e50f9cb2c434b5577cbf8ba1e5c1ed453b6b6558 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 11:07:48 -0500 Subject: [PATCH 020/108] Run formatters --- src/fpds/cli/__init__.py | 3 +-- src/fpds/cli/fields.py | 2 +- src/fpds/cli/parse.py | 2 +- src/fpds/cli/root.py | 1 + src/fpds/core/parser.py | 12 ++++-------- src/fpds/scripts/scraper.py | 15 ++++++++++----- src/fpds/utilities/writer.py | 2 +- 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/fpds/cli/__init__.py b/src/fpds/cli/__init__.py index a3aa354f..e163b146 100644 --- a/src/fpds/cli/__init__.py +++ b/src/fpds/cli/__init__.py @@ -1,4 +1,3 @@ -from fpds.cli.root import app - import fpds.cli.fields import fpds.cli.parse +from fpds.cli.root import app diff --git a/src/fpds/cli/fields.py b/src/fpds/cli/fields.py index e99649e2..8b3f46ae 100644 --- a/src/fpds/cli/fields.py +++ b/src/fpds/cli/fields.py @@ -11,8 +11,8 @@ from tabulate import tabulate from typing_extensions import Annotated -from fpds.config import FPDS_FIELDS_CONFIG from fpds.cli.root import app +from fpds.config import FPDS_FIELDS_CONFIG TEXT_WRAP_WIDTH = 30 diff --git a/src/fpds/cli/parse.py b/src/fpds/cli/parse.py index db2ab17e..ad50788f 100644 --- a/src/fpds/cli/parse.py +++ b/src/fpds/cli/parse.py @@ -13,11 +13,11 @@ from typing_extensions import Annotated from fpds import fpdsRequest +from fpds.cli import app from fpds.cli.root import app from fpds.config import FPDS_DATA_DATE_DIR from fpds.utilities import validate_kwarg -from fpds.cli import app @app.command() def parse( diff --git a/src/fpds/cli/root.py b/src/fpds/cli/root.py index b4f109e8..13e0b6e3 100644 --- a/src/fpds/cli/root.py +++ b/src/fpds/cli/root.py @@ -2,6 +2,7 @@ app = typer.Typer() + @app.callback() def callback(): """Welcome to the fpds CLI 🚀""" diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index a71af0b7..610648ec 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -8,25 +8,26 @@ import asyncio import multiprocessing -from pathlib import Path import warnings from asyncio import Semaphore from concurrent.futures import ProcessPoolExecutor +from pathlib import Path from typing import AsyncGenerator, List, Optional from urllib import parse from urllib.request import urlopen from uuid import uuid4 from httpx import AsyncClient -from rich.progress import TaskID from rich.progress import ( BarColumn, Progress, SpinnerColumn, + TaskID, TextColumn, TimeRemainingColumn, ) +from fpds.config import FPDS_DATA_DATE_DIR from fpds.core import FPDS_ENTRY from fpds.core.mixins import fpdsMixin from fpds.core.xml import fpdsSubTree, fpdsTree @@ -37,8 +38,6 @@ from fpds.utilities import validate_kwarg from fpds.utilities.writer import fpdsChunkWriter -from fpds.config import FPDS_DATA_DATE_DIR - class fpdsRequest(fpdsMixin): """Makes a GET request to the FPDS ATOM feed. @@ -253,9 +252,7 @@ async def iter_data(self) -> AsyncGenerator[FPDS_ENTRY, None]: with ProcessPoolExecutor(max_workers=num_processes) as pool: with self._create_progress() as progress: - task_id = progress.add_task( - "Processing records...", total=len(data) - ) + task_id = progress.add_task("Processing records...", total=len(data)) future_to_record = { pool.submit(self._jsonify, record): record for record in data } @@ -266,7 +263,6 @@ async def iter_data(self) -> AsyncGenerator[FPDS_ENTRY, None]: for entry in result: yield entry - async def data(self, output_dir: Path = FPDS_DATA_DATE_DIR) -> None: """Outputs FPDS data as partitioned-sized JSON gzip files. diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index ac2115fd..8e0525ae 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -4,14 +4,14 @@ last_updated: 2025-12-11 """ -from pathlib import Path import json +from pathlib import Path from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait from fpds.config import FPDS_EZSEARCH_URL, FPDS_FIELDS_FILE_PATH @@ -30,7 +30,8 @@ def update_fields_json(dropdown_fields): "quotes": False, "regex": "", } - for field in dropdown_fields if field not in current_field_options + for field in dropdown_fields + if field not in current_field_options ] config.extend(new_options) sorted_config = sorted(config, key=lambda field: field["name"]) @@ -39,6 +40,7 @@ def update_fields_json(dropdown_fields): # with Path(FPDS_FIELDS_FILE_PATH).open(mode="w", encoding="utf-8") as file: # json.dump(sorted_config, file, indent=4) + def scrape_ezsearch(): """Scrapes FPDS ezSearch field for Advanced Search Criteria dropdown values. @@ -64,17 +66,20 @@ def scrape_ezsearch(): add_button.click() dropdowns = WebDriverWait(driver, 10).until( - EC.presence_of_all_elements_located((By.CSS_SELECTOR, "#advancedSearchdiv select")) + EC.presence_of_all_elements_located( + (By.CSS_SELECTOR, "#advancedSearchdiv select") + ) ) dropdown_fields = [] for dropdown in dropdowns: options = dropdown.find_elements(By.TAG_NAME, "option") for opt in options: - dropdown_fields.append(opt.get_attribute('value')) + dropdown_fields.append(opt.get_attribute("value")) return dropdown_fields + if __name__ == "__main__": dropdown_fields = scrape_ezsearch() update_fields_json(dropdown_fields=dropdown_fields) diff --git a/src/fpds/utilities/writer.py b/src/fpds/utilities/writer.py index 218f1f28..fea353fd 100644 --- a/src/fpds/utilities/writer.py +++ b/src/fpds/utilities/writer.py @@ -13,6 +13,7 @@ from fpds.core import FPDS_ENTRY + class fpdsChunkWriter: def __init__( self, @@ -49,7 +50,6 @@ async def chunkify(self, entries): return file_paths - def flush(self, chunk_buffer: list[FPDS_ENTRY]) -> Path: file_path = self.output_dir / f"{uuid4()}.json.gz" with gzip.open(file_path, "wt", encoding="utf-8") as gz_file: From 761dd9a1dec657bba97e311e60e195155a98169d Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 11:13:54 -0500 Subject: [PATCH 021/108] Fix return type --- src/fpds/cli/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpds/cli/root.py b/src/fpds/cli/root.py index 13e0b6e3..c6c4b0fd 100644 --- a/src/fpds/cli/root.py +++ b/src/fpds/cli/root.py @@ -4,5 +4,5 @@ @app.callback() -def callback(): +def callback() -> None: """Welcome to the fpds CLI 🚀""" From e005c798a5911139b1ed55ec96f996f30ac6887b Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 11:16:16 -0500 Subject: [PATCH 022/108] Fix circular import --- src/fpds/cli/parse.py | 1 - tests/test_cli.py | 2 +- tests/test_parser.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/fpds/cli/parse.py b/src/fpds/cli/parse.py index ad50788f..a47a4d31 100644 --- a/src/fpds/cli/parse.py +++ b/src/fpds/cli/parse.py @@ -13,7 +13,6 @@ from typing_extensions import Annotated from fpds import fpdsRequest -from fpds.cli import app from fpds.cli.root import app from fpds.config import FPDS_DATA_DATE_DIR from fpds.utilities import validate_kwarg diff --git a/tests/test_cli.py b/tests/test_cli.py index 78e0f742..14bc0124 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -33,7 +33,7 @@ def test_parse_with_custom_output_dir(self): "AGENCY_CODE=7504", "-o", "./test", - ] + ], ) self.assertEqual(result.exit_code, 0) diff --git a/tests/test_parser.py b/tests/test_parser.py index 6db4714b..28ebcf4a 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -43,6 +43,7 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, traceback): pass + class MockFpdsXML(object): def pagination_links(self, params="some-param1: param1-value"): return [ @@ -84,7 +85,6 @@ def test_max_page_length_exceeded_error_raised(self, mock_urlopen): page=1_000_000, ) - @mock.patch("fpds.core.parser.urlopen") def test_skip_regex_validation_warning_raised(self, mock_urlopen): mock_urlopen.return_value = MockHTTPResponse() From b1be3293868d7b56f146e2da4370d9178aaf0287 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 18:35:36 -0500 Subject: [PATCH 023/108] Fixing type issues where relevant --- src/fpds/cli/__init__.py | 3 ++- src/fpds/cli/fields.py | 5 +++-- src/fpds/cli/parse.py | 13 +++++++------ src/fpds/core/parser.py | 7 ++----- src/fpds/scripts/scraper.py | 16 +++++++++------- src/fpds/utilities/writer.py | 23 ++++++++++++++++------- 6 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/fpds/cli/__init__.py b/src/fpds/cli/__init__.py index e163b146..a3aa354f 100644 --- a/src/fpds/cli/__init__.py +++ b/src/fpds/cli/__init__.py @@ -1,3 +1,4 @@ +from fpds.cli.root import app + import fpds.cli.fields import fpds.cli.parse -from fpds.cli.root import app diff --git a/src/fpds/cli/fields.py b/src/fpds/cli/fields.py index 8b3f46ae..48762fb8 100644 --- a/src/fpds/cli/fields.py +++ b/src/fpds/cli/fields.py @@ -8,6 +8,7 @@ import textwrap import typer +from typing import Optional from tabulate import tabulate from typing_extensions import Annotated @@ -20,7 +21,7 @@ @app.command() def fields( pattern: Annotated[ - str, + Optional[str], typer.Option( "--pattern", "-p", @@ -33,7 +34,7 @@ def fields( width: Annotated[ int, typer.Option("--width", "-w", help="Text wrap width for regex field.") ] = TEXT_WRAP_WIDTH, -): +) -> None: """Displays list of available FPDS fields and their descriptions. \b diff --git a/src/fpds/cli/parse.py b/src/fpds/cli/parse.py index a47a4d31..f0af66a7 100644 --- a/src/fpds/cli/parse.py +++ b/src/fpds/cli/parse.py @@ -35,10 +35,12 @@ def parse( ..., help="Positional parameters (variadic, like nargs=-1)", ), -): +) -> None: """Sends ATOM feed request to FPDS.""" - if not output_dir.exists(): - output_dir.mkdir(parents=True, exist_ok=True) + + if output_dir: + if not output_dir.exists(): + output_dir.mkdir(parents=True, exist_ok=True) split_params = [param.split("=") for param in params] @@ -47,6 +49,5 @@ def parse( _param[1] = validate_kwarg(kwarg=name, string=value) params_kwargs = dict(split_params) - - request = fpdsRequest(**params_kwargs, cli_run=True) - asyncio.run(request.data(output_dir=str(output_dir))) + request = fpdsRequest(cli_run=True, **params_kwargs) # type: ignore[arg-type] + asyncio.run(request.data(output_dir=output_dir)) diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index 610648ec..cdca56f8 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -12,7 +12,7 @@ from asyncio import Semaphore from concurrent.futures import ProcessPoolExecutor from pathlib import Path -from typing import AsyncGenerator, List, Optional +from typing import AsyncGenerator, List, Optional, overload from urllib import parse from urllib.request import urlopen from uuid import uuid4 @@ -95,7 +95,6 @@ class fpdsRequest(fpdsMixin): fpdsMissingKeywordParameterError: Raised if no keyword argument(s) are provided. """ - def __init__( self, cli_run: bool = False, @@ -156,7 +155,6 @@ def page_count(self) -> int: """Total number of FPDS pages contained in request.""" return len(self.links) - @staticmethod def mb_to_bytes(self) -> int: return self.max_chunk_size_mb * 1_048_576 @@ -280,6 +278,5 @@ async def data(self, output_dir: Path = FPDS_DATA_DATE_DIR) -> None: output_dir=output_path, max_chunk_size_mb=self.max_chunk_size_mb, ) - file_paths = await writer.chunkify(self.iter_data()) + await writer.chunkify(self.iter_data()) print(f"Wrote records to {output_path}") - return file_paths diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 8e0525ae..e65d6caf 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -6,6 +6,7 @@ import json from pathlib import Path +from typing import List from selenium import webdriver from selenium.webdriver.chrome.options import Options @@ -16,8 +17,8 @@ from fpds.config import FPDS_EZSEARCH_URL, FPDS_FIELDS_FILE_PATH -def update_fields_json(dropdown_fields): - with Path(FPDS_FIELDS_FILE_PATH).open(encoding="utf-8") as file: +def update_fields_json(dropdown_fields: List[str]) -> None: + with Path(str(FPDS_FIELDS_FILE_PATH)).open(encoding="utf-8") as file: config = json.load(file) current_field_options = [field["name"] for field in config] @@ -36,12 +37,11 @@ def update_fields_json(dropdown_fields): config.extend(new_options) sorted_config = sorted(config, key=lambda field: field["name"]) - print(new_options) # with Path(FPDS_FIELDS_FILE_PATH).open(mode="w", encoding="utf-8") as file: # json.dump(sorted_config, file, indent=4) -def scrape_ezsearch(): +def scrape_ezsearch() -> List[str]: """Scrapes FPDS ezSearch field for Advanced Search Criteria dropdown values. These values represent valid parameter values for an instance of @@ -73,9 +73,11 @@ def scrape_ezsearch(): dropdown_fields = [] for dropdown in dropdowns: - options = dropdown.find_elements(By.TAG_NAME, "option") - for opt in options: - dropdown_fields.append(opt.get_attribute("value")) + element = dropdown.find_elements(By.TAG_NAME, "option") + for opt in element: + value = opt.get_attribute("value") + if value: + dropdown_fields.append(value) return dropdown_fields diff --git a/src/fpds/utilities/writer.py b/src/fpds/utilities/writer.py index fea353fd..6fba4503 100644 --- a/src/fpds/utilities/writer.py +++ b/src/fpds/utilities/writer.py @@ -8,13 +8,22 @@ import gzip import json from pathlib import Path -from typing import List +from typing import AsyncGenerator, List from uuid import uuid4 from fpds.core import FPDS_ENTRY class fpdsChunkWriter: + """Chunks FPDS request data into JSON gzip files. + + Attributes + ---------- + output_dir: `Path` + Output directory. + max_chunk_size_mb: `int` + The maximum size of each outputted data file (uncompressed). + """ def __init__( self, output_dir: Path, @@ -26,11 +35,12 @@ def __init__( @staticmethod def record_size(entry: FPDS_ENTRY) -> int: + """Calculates the size of an FPDS entry record.""" entry_json = json.dumps(entry) return len(entry_json.encode("utf-8")) - async def chunkify(self, entries): - file_paths: list[Path] = [] + async def chunkify(self, entries: AsyncGenerator[FPDS_ENTRY, None]) -> None: + """Chunkifies FPDS entries into JSON gzip files of :max_chunk_size_mb: size.""" chunk: List[FPDS_ENTRY] = [] current_size = 0 @@ -38,7 +48,7 @@ async def chunkify(self, entries): size = self.record_size(entry) if current_size + size > self.max_bytes and chunk: - file_paths.append(self.flush(chunk)) + self.flush(chunk) chunk = [entry] current_size = size else: @@ -46,11 +56,10 @@ async def chunkify(self, entries): current_size += size if chunk: - file_paths.append(self.flush(chunk)) + self.flush(chunk) - return file_paths - def flush(self, chunk_buffer: list[FPDS_ENTRY]) -> Path: + def flush(self, chunk_buffer: list[FPDS_ENTRY]) -> None: file_path = self.output_dir / f"{uuid4()}.json.gz" with gzip.open(file_path, "wt", encoding="utf-8") as gz_file: json.dump(chunk_buffer, gz_file) From 434855d26ea960e5b7b89d2418565362c42ccb06 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 18:43:37 -0500 Subject: [PATCH 024/108] Fix last type issue --- src/fpds/cli/parse.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fpds/cli/parse.py b/src/fpds/cli/parse.py index f0af66a7..bb297e4a 100644 --- a/src/fpds/cli/parse.py +++ b/src/fpds/cli/parse.py @@ -41,6 +41,7 @@ def parse( if output_dir: if not output_dir.exists(): output_dir.mkdir(parents=True, exist_ok=True) + dir = Path(output_dir) split_params = [param.split("=") for param in params] @@ -50,4 +51,4 @@ def parse( params_kwargs = dict(split_params) request = fpdsRequest(cli_run=True, **params_kwargs) # type: ignore[arg-type] - asyncio.run(request.data(output_dir=output_dir)) + asyncio.run(request.data(output_dir=dir)) From 1b07b73baaa109e9f454631f601b27fcd9e4ba7c Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 21:28:27 -0500 Subject: [PATCH 025/108] update README and add new xml test file with no links --- README.md | 32 ++++++++++++++++++++------------ tests/data/no_link_response.xml | 10 ++++++++++ 2 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 tests/data/no_link_response.xml diff --git a/README.md b/README.md index 6a79519d..e6bb779e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# fpds +
+ __________ ____ _____ / ____/ __ \/ __ \/ ___/ @@ -9,19 +10,22 @@ Welcome to a more user-friendly FPDS 🚀 A light-weight, pythonic parser for the Federal Procurement Data System (FPDS) ATOM Feed. Reference [here](https://www.fpds.gov/fpdsng_cms/index.php/en/). +
## Motivation -The FPDS ATOM feed limits each request to 10 records, which forces users to deal -with pagination. Additonally, data is exported as XML, which proves annoying. -`fpds` will handle all pagination and data transformation to provide users with -a nice JSON representation of the equivalent XML data and attributes. +To make FPDS data more accesible to developers. + +This library helps users by doing the following: +- Automatically handling pagination +- Converting XML and all associated attributes into JSON format ## Setup As of version 1.5.0, this library manages dependencies using `uv`. It is -_highly_ recommended since this library is tested with it. - +_highly_ recommended since this library is tested with it. Note that this +README assumes you will install `uv` and therefore runs all commands within +its context. ### Installing `uv` @@ -52,6 +56,9 @@ $ make clean ``` ### Testing + +Run unit tests on your local environment: + ``` $ make local-test ``` @@ -96,10 +103,11 @@ intentional. $ uv run fpds parse -k "A_NEW_PARAM=a-new-value" ``` -As of v1.6.0, the `fields` command provides a quick reference of available -filtering fields. To print them out, run the following command. Use the `--export` -flag to export the full metadata for fields (this is helpful if you wish to see -quoting configuration and the regex validation pattern). +Let's say you ran the above command, but were unsure about the quoting strategy +and you wanted to run this using the CLI. As of v1.6.0, the `fields` command +provides a quick reference of available filtering fields. To print them out, run +the following command. Use the `--export` flag to export the full metadata for +fields (this is helpful if you wish to see quoting configuration and the regex validation pattern). ``` $ uv run fpds fields --export true @@ -149,4 +157,4 @@ This equates to a **84.89%** decrease in completion time! # Notes Please be aware that this project is an after-hours passion of mine. I do my best -to accomodate requests the best I can, but I receive no $$$ for any of the work I do here. +to accomodate requests, but I receive no $$$ for any of the work I do here. diff --git a/tests/data/no_link_response.xml b/tests/data/no_link_response.xml new file mode 100644 index 00000000..388e6bad --- /dev/null +++ b/tests/data/no_link_response.xml @@ -0,0 +1,10 @@ + + FPDS-NG search results for + + + + + + + + \ No newline at end of file From 3442646fc1140abed808bc53492cae36c51b5306 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 21:30:32 -0500 Subject: [PATCH 026/108] remove coment --- src/fpds/cli/parse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpds/cli/parse.py b/src/fpds/cli/parse.py index bb297e4a..dad77ba4 100644 --- a/src/fpds/cli/parse.py +++ b/src/fpds/cli/parse.py @@ -45,7 +45,7 @@ def parse( split_params = [param.split("=") for param in params] - for _param in split_params: # _param is a tuple + for _param in split_params: name, value = _param _param[1] = validate_kwarg(kwarg=name, string=value) From bec69d1833e8cebb277cc20ce6e9377c02b20479 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 23:25:08 -0500 Subject: [PATCH 027/108] test xml --- tests/test_xml.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/test_xml.py b/tests/test_xml.py index a19432e7..05edd817 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -59,6 +59,25 @@ def test_jsonify(self): class TestFpdsElement(TestCase): def setUp(self): - xml = fpdsTree(content=FULL_RESPONSE_DATA_BYTES) - element = xml.get_atom_feed_entries()[0] - self._class = fpdsElement(content=element) + self.xml = fpdsTree(content=FULL_RESPONSE_DATA_BYTES) + self.element = self.xml.get_atom_feed_entries()[0] + self.fpds_element = fpdsElement( + element=self.element, + namespace_dict=TEST_NAMESPACE_DICT + ) + + def test_iter(self): + iterator = iter(self.fpds_element) + self.assertTrue(hasattr(iterator, "__next__")) + + def test_len(self): + self.assertEqual(len(self.element), len(self.fpds_element)) + + def test_getitem(self): + self.assertEqual(self.fpds_element[0], self.element[0]) + + def test_parse_items(self): + self.assertEqual( + list(self.fpds_element.parse_items()), + list(self.element.iter()), + ) From 57bc79f93b6d1cf641a02aa3865cce2eed9c87be Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sun, 14 Dec 2025 23:39:51 -0500 Subject: [PATCH 028/108] minor: update docstring --- src/fpds/core/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index cdca56f8..4156008f 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -3,7 +3,7 @@ tree into JSON. author: derek663@gmail.com -last_updated: 2025-07-14 +last_updated: 2025-12-14 """ import asyncio From 04194e61f674ba022c659e86d23eb1779c6bb5f2 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 20:18:17 -0500 Subject: [PATCH 029/108] Adding GHA step for installing just and modified tests --- .github/workflows/pr-for-new-fields.yml | 12 ++++++ .github/workflows/publish.yml | 10 +++-- .github/workflows/test-package.yml | 8 +++- Makefile => Justfile | 7 +--- tests/__init__.py | 4 ++ tests/test_cli.py | 26 ++++++------ tests/test_parser.py | 56 +++++++++++++++++++------ 7 files changed, 88 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/pr-for-new-fields.yml rename Makefile => Justfile (84%) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml new file mode 100644 index 00000000..76ac0af4 --- /dev/null +++ b/.github/workflows/pr-for-new-fields.yml @@ -0,0 +1,12 @@ +name: New fpds search fields PR + +on: + workflow_dispatch: # Manual trigger + schedule: + - cron: "0 12 * * *" # Daily at 12:00 UTC + +jobs: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d58f8fbb..6ee5b133 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -59,16 +59,20 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 + - uses: extractions/setup-just@v3 + with: + just-version: 1.46.0 + - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Project - run: make install + run: just install - name: Run Tests - run: make test + run: just test publish: needs: [cut-release, build-and-test] @@ -85,4 +89,4 @@ jobs: - name: Publish Package env: UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} - run: make publish + run: just publish diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 7f11ad5a..0f6a10ef 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -22,16 +22,20 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 + - uses: extractions/setup-just@v3 + with: + just-version: 1.46.0 + - name: Setup Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Project - run: make install + run: just install - name: Run Tests - run: make test + run: just test - uses: actions/checkout@v4 with: diff --git a/Makefile b/Justfile similarity index 84% rename from Makefile rename to Justfile index 0b3ef4be..c9aac54d 100644 --- a/Makefile +++ b/Justfile @@ -1,8 +1,5 @@ -.PHONY: help venv install clean formatters mypy test local-test package publish -.DEFAULT_GOAL := help - -help: - @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) +default: + just --list venv: ## defaults to creating virtual environment in current directory under .venv @if [ -d .venv ]; then \ diff --git a/tests/__init__.py b/tests/__init__.py index cd2873ee..4228e0a9 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -19,7 +19,11 @@ FPDS_TRUNCATED_XML_TEST_DATA_FILE = files(FPDS_TEST_PACKAGE).joinpath( "truncated_response.xml" ) + FPDS_NO_LINK_XML_TEST_DATA_FILE = files(FPDS_TEST_PACKAGE).joinpath( + "no_link_response.xml" + ) # XML sample responses FULL_RESPONSE_DATA_BYTES = read_xml_as_bytes(FPDS_XML_TEST_DATA_FILE) TRUNCATED_RESPONSE_DATA_BYTES = read_xml_as_bytes(FPDS_TRUNCATED_XML_TEST_DATA_FILE) +NO_LINK_RESPONSE_DATA_BYTES = read_xml_as_bytes(FPDS_NO_LINK_XML_TEST_DATA_FILE) \ No newline at end of file diff --git a/tests/test_cli.py b/tests/test_cli.py index 14bc0124..2e28bae3 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -6,7 +6,7 @@ from fpds.cli import app -class TestFpdsCLI(TestCase): +class TestFpdsParseCommand(TestCase): def setUp(self): self.runner = CliRunner() @@ -24,18 +24,18 @@ def test_invalid_cli_parameter_regex_pattern(self): result = self.runner.invoke(app, ["parse", "AGENCY_CODE={not-valid}"]) self.assertIn("does not match regex", result.__str__()) - def test_parse_with_custom_output_dir(self): - with self.runner.isolated_filesystem(): - result = self.runner.invoke( - app, - [ - "parse", - "AGENCY_CODE=7504", - "-o", - "./test", - ], - ) - self.assertEqual(result.exit_code, 0) + # def test_parse_with_custom_output_dir(self): + # with self.runner.isolated_filesystem(): + # result = self.runner.invoke( + # app, + # [ + # "parse", + # "AGENCY_CODE=7504", + # "-o", + # "./test", + # ], + # ) + # self.assertEqual(result.exit_code, 0) if __name__ == "__main__": diff --git a/tests/test_parser.py b/tests/test_parser.py index 28ebcf4a..b66d9fdf 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,15 +1,18 @@ +import asyncio import unittest from unittest import TestCase, mock from xml.etree.ElementTree import ElementTree, fromstring from fpds import fpdsRequest from fpds.errors import ( + fpdsDuplicateParameterConfiguration, fpdsInvalidParameter, fpdsMaxPageLengthExceededError, fpdsMismatchedParameterRegexError, fpdsMissingKeywordParameterError, ) -from tests import FULL_RESPONSE_DATA_BYTES +from fpds.core.xml import fpdsSubTree +from tests import FULL_RESPONSE_DATA_BYTES, NO_LINK_RESPONSE_DATA_BYTES # valid params and values FPDS_REQUEST_PARAMS_DICT = { @@ -34,8 +37,11 @@ class MockHTTPResponse: + def __init__(self, content: bytes = FULL_RESPONSE_DATA_BYTES): + self.content = content + def read(self): - return FULL_RESPONSE_DATA_BYTES + return self.content def __enter__(self): return self @@ -44,20 +50,46 @@ def __exit__(self, exc_type, exc_value, traceback): pass -class MockFpdsXML(object): - def pagination_links(self, params="some-param1: param1-value"): - return [ - "{some-fpds-link}&start=0", - "{some-fpds-link}&start=10", - "{some-fpds-link}&start=20", - ] - - class TestFpdsRequest(TestCase): - def test_params_exist(self): + def test_parameter_error_raised_with_no_kwargs(self): with self.assertRaises(fpdsMissingKeywordParameterError): fpdsRequest({}) + @mock.patch("fpds.core.parser.urlopen") + def test_mb_to_bytes(self, mock_urlopen): + """Test that mb_to_bytes correctly converts megabytes to bytes.""" + mock_urlopen.return_value = MockHTTPResponse() + req = fpdsRequest(**FPDS_REQUEST_PARAMS_DICT, max_chunk_size_mb=50) + self.assertEqual(req.mb_to_bytes(), 50 * 1_048_576) + + @mock.patch("fpds.core.parser.urlopen") + def test_no_pagination_links(self, mock_urlopen): + """Test that initial_request correctly returns the root XML tree from the initial request.""" + mock_urlopen.return_value = MockHTTPResponse(content=NO_LINK_RESPONSE_DATA_BYTES) + req = fpdsRequest(**FPDS_REQUEST_PARAMS_DICT) + self.assertEqual(asyncio.run(req.fetch()), []) + + # @mock.patch("fpds.core.parser.urlopen") + # def test_convert(self, mock_urlopen): + # """Test that initial_request correctly returns the root XML tree from the initial request.""" + # mock_urlopen.return_value = MockHTTPResponse() + # req = fpdsRequest(**FPDS_REQUEST_PARAMS_DICT) + # mock_client = mock.AsyncMock() + # mock_client.get.return_value = MockHTTPResponse(content=FULL_RESPONSE_DATA_BYTES) + + # from asyncio import Semaphore + # semaphore = Semaphore(10) + # result = asyncio.run( + # req.convert( + # client=mock_client, + # link="https://example.com/fpds", + # semaphore=semaphore, + # ) + # ) + + # # Assertions + # self.assertIsInstance(result, fpdsSubTree) + @mock.patch("fpds.core.parser.urlopen") def test_request_link_count(self, mock_urlopen): """Test that generated number of links from initial request is correct.""" From 9ff407348c4aaa528d8e5b456d1b6c175f927c4a Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 20:43:05 -0500 Subject: [PATCH 030/108] Install webdriver manager package --- .github/workflows/pr-for-new-fields.yml | 18 +++++++++++++---- pyproject.toml | 1 + uv.lock | 26 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 76ac0af4..b79647c5 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -1,12 +1,22 @@ name: New fpds search fields PR on: - workflow_dispatch: # Manual trigger + workflow_dispatch: schedule: - - cron: "0 12 * * *" # Daily at 12:00 UTC + - cron: "0 12 * * *" jobs: + scrape: runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Installed package list + run: apt list --installed + - name: Remove Chrome + run: sudo apt purge google-chrome-stable + - name: Remove default Chromium + run: sudo apt purge chromium-browser + - name: Install a new Chromium + run: sudo apt install -y chromium-browser diff --git a/pyproject.toml b/pyproject.toml index e32e422c..4d77a690 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ Issues = "https://github.com/dherincx92/fpds/issues" [project.optional-dependencies] scripts = [ "selenium>=4.0.0,<5.0.0", + "webdriver-manager>=4.0.0,<5.0.0", ] dev = [ "ipdb==0.13.9", diff --git a/uv.lock b/uv.lock index f635d588..430bfe50 100644 --- a/uv.lock +++ b/uv.lock @@ -429,6 +429,7 @@ all = [ { name = "types-tabulate" }, { name = "types-tqdm" }, { name = "versioningit" }, + { name = "webdriver-manager" }, { name = "wheel" }, ] dev = [ @@ -447,6 +448,7 @@ packaging = [ ] scripts = [ { name = "selenium" }, + { name = "webdriver-manager" }, ] tests = [ { name = "pytest" }, @@ -478,6 +480,7 @@ requires-dist = [ { name = "types-tqdm", marker = "extra == 'dev'", specifier = "==4.64.7.9" }, { name = "typing-extensions", specifier = ">=4.15.0" }, { name = "versioningit", marker = "extra == 'dev'", specifier = ">=3.0.0,<4.0.0" }, + { name = "webdriver-manager", marker = "extra == 'scripts'", specifier = ">=4.0.0,<5.0.0" }, { name = "wheel", marker = "extra == 'packaging'", specifier = "==0.37.1" }, ] provides-extras = ["scripts", "dev", "tests", "packaging", "all"] @@ -1038,6 +1041,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/7b/1cec26caae4bf44bb9911e1119d5d1a35171571e100b728a2ccd8719a3b1/pytest_runner-6.0.0-py3-none-any.whl", hash = "sha256:4c059cf11cf4306e369c0f8f703d1eaf8f32fad370f41deb5f007044656aca6b", size = 7218, upload-time = "2022-02-25T19:21:09.422Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -1419,6 +1431,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, ] +[[package]] +name = "webdriver-manager" +version = "4.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "python-dotenv" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/4f/6e44478908c5133f680378d687f14ecaa99feed2c535344fcf68d8d21500/webdriver_manager-4.0.2.tar.gz", hash = "sha256:efedf428f92fd6d5c924a0d054e6d1322dd77aab790e834ee767af392b35590f", size = 25940, upload-time = "2024-07-25T08:13:49.331Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/b5/3bd0b038d80950ec13e6a2c8d03ed8354867dc60064b172f2f4ffac8afbe/webdriver_manager-4.0.2-py2.py3-none-any.whl", hash = "sha256:75908d92ecc45ff2b9953614459c633db8f9aa1ff30181cefe8696e312908129", size = 27778, upload-time = "2024-07-25T08:13:47.917Z" }, +] + [[package]] name = "websocket-client" version = "1.9.0" From 6ffd2e3c61abfce1b93916f0c618dc9439951215 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 20:46:25 -0500 Subject: [PATCH 031/108] add trigger for new fields PR --- .github/workflows/pr-for-new-fields.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index b79647c5..e1292be5 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -1,6 +1,9 @@ name: New fpds search fields PR on: + push: + branches: + - master workflow_dispatch: schedule: - cron: "0 12 * * *" From 4000175c4c3f5d393aed4188ae1e11dc938cc489 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 20:48:52 -0500 Subject: [PATCH 032/108] minor: remove webdriver_manager package --- pyproject.toml | 1 - uv.lock | 26 -------------------------- 2 files changed, 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4d77a690..e32e422c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,6 @@ Issues = "https://github.com/dherincx92/fpds/issues" [project.optional-dependencies] scripts = [ "selenium>=4.0.0,<5.0.0", - "webdriver-manager>=4.0.0,<5.0.0", ] dev = [ "ipdb==0.13.9", diff --git a/uv.lock b/uv.lock index 430bfe50..f635d588 100644 --- a/uv.lock +++ b/uv.lock @@ -429,7 +429,6 @@ all = [ { name = "types-tabulate" }, { name = "types-tqdm" }, { name = "versioningit" }, - { name = "webdriver-manager" }, { name = "wheel" }, ] dev = [ @@ -448,7 +447,6 @@ packaging = [ ] scripts = [ { name = "selenium" }, - { name = "webdriver-manager" }, ] tests = [ { name = "pytest" }, @@ -480,7 +478,6 @@ requires-dist = [ { name = "types-tqdm", marker = "extra == 'dev'", specifier = "==4.64.7.9" }, { name = "typing-extensions", specifier = ">=4.15.0" }, { name = "versioningit", marker = "extra == 'dev'", specifier = ">=3.0.0,<4.0.0" }, - { name = "webdriver-manager", marker = "extra == 'scripts'", specifier = ">=4.0.0,<5.0.0" }, { name = "wheel", marker = "extra == 'packaging'", specifier = "==0.37.1" }, ] provides-extras = ["scripts", "dev", "tests", "packaging", "all"] @@ -1041,15 +1038,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/7b/1cec26caae4bf44bb9911e1119d5d1a35171571e100b728a2ccd8719a3b1/pytest_runner-6.0.0-py3-none-any.whl", hash = "sha256:4c059cf11cf4306e369c0f8f703d1eaf8f32fad370f41deb5f007044656aca6b", size = 7218, upload-time = "2022-02-25T19:21:09.422Z" }, ] -[[package]] -name = "python-dotenv" -version = "1.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, -] - [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -1431,20 +1419,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, ] -[[package]] -name = "webdriver-manager" -version = "4.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "python-dotenv" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/24/4f/6e44478908c5133f680378d687f14ecaa99feed2c535344fcf68d8d21500/webdriver_manager-4.0.2.tar.gz", hash = "sha256:efedf428f92fd6d5c924a0d054e6d1322dd77aab790e834ee767af392b35590f", size = 25940, upload-time = "2024-07-25T08:13:49.331Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/b5/3bd0b038d80950ec13e6a2c8d03ed8354867dc60064b172f2f4ffac8afbe/webdriver_manager-4.0.2-py2.py3-none-any.whl", hash = "sha256:75908d92ecc45ff2b9953614459c633db8f9aa1ff30181cefe8696e312908129", size = 27778, upload-time = "2024-07-25T08:13:47.917Z" }, -] - [[package]] name = "websocket-client" version = "1.9.0" From 98cd31865b0e680e718d33007932fc2edd307f8a Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 20:56:50 -0500 Subject: [PATCH 033/108] Fixing yml file workflow --- .github/workflows/pr-for-new-fields.yml | 6 +----- src/fpds/scripts/__init__.py | 0 src/fpds/scripts/scraper.py | 2 ++ 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 src/fpds/scripts/__init__.py diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index e1292be5..b9c8cf7f 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -1,12 +1,8 @@ name: New fpds search fields PR on: - push: - branches: - - master + pull_request: workflow_dispatch: - schedule: - - cron: "0 12 * * *" jobs: scrape: diff --git a/src/fpds/scripts/__init__.py b/src/fpds/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index e65d6caf..22018d1f 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -36,6 +36,8 @@ def update_fields_json(dropdown_fields: List[str]) -> None: ] config.extend(new_options) sorted_config = sorted(config, key=lambda field: field["name"]) + from pprint import pprint + pprint(sorted_config) # with Path(FPDS_FIELDS_FILE_PATH).open(mode="w", encoding="utf-8") as file: # json.dump(sorted_config, file, indent=4) From c414dcd1d4ad5b46f5e7ed2c17d061a161bfd68b Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 20:59:13 -0500 Subject: [PATCH 034/108] Add pprint to scraper --- Justfile | 3 +++ src/fpds/scripts/scraper.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Justfile b/Justfile index c9aac54d..6ad6c1f2 100644 --- a/Justfile +++ b/Justfile @@ -37,3 +37,6 @@ package: ## builds project + artifacts in dist/ directory publish: package ## publishes package to pypi uv publish + +scrape: venv install + uv run python src/fpds/scripts/scraper.py \ No newline at end of file diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 22018d1f..7a9a31b0 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -36,9 +36,9 @@ def update_fields_json(dropdown_fields: List[str]) -> None: ] config.extend(new_options) sorted_config = sorted(config, key=lambda field: field["name"]) + from pprint import pprint pprint(sorted_config) - # with Path(FPDS_FIELDS_FILE_PATH).open(mode="w", encoding="utf-8") as file: # json.dump(sorted_config, file, indent=4) From 5b9ef2d9b5eba847ae8b045806d43db6a41332fd Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 21:03:20 -0500 Subject: [PATCH 035/108] Using setup-chromedriver job --- .github/workflows/pr-for-new-fields.yml | 28 ++++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index b9c8cf7f..8a51eb8d 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -11,11 +11,23 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Installed package list - run: apt list --installed - - name: Remove Chrome - run: sudo apt purge google-chrome-stable - - name: Remove default Chromium - run: sudo apt purge chromium-browser - - name: Install a new Chromium - run: sudo apt install -y chromium-browser + - uses: nanasess/setup-chromedriver@v2 + - uses: extractions/setup-just@v3 + with: + just-version: 1.46.0 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.13 + + - name: Scrape FPDS Site + run: just scrape + # - name: Installed package list + # run: apt list --installed + # - name: Remove Chrome + # run: sudo apt purge google-chrome-stable + # - name: Remove default Chromium + # run: sudo apt purge chromium-browser + # - name: Install a new Chromium + # run: sudo apt install -y chromium-browser From b66578f0980a2238423472b2a5802a9ec845ed4c Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 21:04:25 -0500 Subject: [PATCH 036/108] Add step for installing uv --- .github/workflows/pr-for-new-fields.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 8a51eb8d..e9a2253f 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -12,6 +12,9 @@ jobs: with: fetch-depth: 0 - uses: nanasess/setup-chromedriver@v2 + + - name: Install uv + uses: astral-sh/setup-uv@v5 - uses: extractions/setup-just@v3 with: just-version: 1.46.0 From 0af6de251e5bf403f2949db94c67f54e2389715f Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 21:17:02 -0500 Subject: [PATCH 037/108] pprint new options to test script --- src/fpds/scripts/scraper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 7a9a31b0..12ccc49d 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -38,7 +38,7 @@ def update_fields_json(dropdown_fields: List[str]) -> None: sorted_config = sorted(config, key=lambda field: field["name"]) from pprint import pprint - pprint(sorted_config) + pprint(new_options) # with Path(FPDS_FIELDS_FILE_PATH).open(mode="w", encoding="utf-8") as file: # json.dump(sorted_config, file, indent=4) From 1cf31040d48dbd5a288537d2c83097c132faf876 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 21:26:36 -0500 Subject: [PATCH 038/108] minor: update scraper --- src/fpds/scripts/scraper.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 12ccc49d..8bc744f8 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -1,7 +1,7 @@ """Scrapes fields from FPDS ezSearch page. author: derek663@gmail.com -last_updated: 2025-12-11 +last_updated: 2026-01-06 """ import json @@ -37,10 +37,8 @@ def update_fields_json(dropdown_fields: List[str]) -> None: config.extend(new_options) sorted_config = sorted(config, key=lambda field: field["name"]) - from pprint import pprint - pprint(new_options) - # with Path(FPDS_FIELDS_FILE_PATH).open(mode="w", encoding="utf-8") as file: - # json.dump(sorted_config, file, indent=4) + with Path(FPDS_FIELDS_FILE_PATH).open(mode="w", encoding="utf-8") as file: + json.dump(sorted_config, file, indent=4) def scrape_ezsearch() -> List[str]: From 724b102305774e164b1905d97f6ff0ee8c9f0ec0 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 21:36:30 -0500 Subject: [PATCH 039/108] Add EOL --- tests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/__init__.py b/tests/__init__.py index 4228e0a9..83221558 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -26,4 +26,4 @@ # XML sample responses FULL_RESPONSE_DATA_BYTES = read_xml_as_bytes(FPDS_XML_TEST_DATA_FILE) TRUNCATED_RESPONSE_DATA_BYTES = read_xml_as_bytes(FPDS_TRUNCATED_XML_TEST_DATA_FILE) -NO_LINK_RESPONSE_DATA_BYTES = read_xml_as_bytes(FPDS_NO_LINK_XML_TEST_DATA_FILE) \ No newline at end of file +NO_LINK_RESPONSE_DATA_BYTES = read_xml_as_bytes(FPDS_NO_LINK_XML_TEST_DATA_FILE) From 1be61f2e08edb5a216205e011442c34f1d3d9754 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 21:54:01 -0500 Subject: [PATCH 040/108] Update GHA for new fields --- .github/pull_request_template.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..e69de29b From 6b1752fe6183aa022c732d29ffa941e8ca6e4ca5 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 21:59:44 -0500 Subject: [PATCH 041/108] Thanks to Leon; updating types based on PEP-0604 --- src/fpds/cli/fields.py | 2 +- src/fpds/core/parser.py | 6 +++--- src/fpds/core/xml.py | 8 ++++---- src/fpds/scripts/scraper.py | 2 +- src/fpds/utilities/params.py | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/fpds/cli/fields.py b/src/fpds/cli/fields.py index 48762fb8..f1864366 100644 --- a/src/fpds/cli/fields.py +++ b/src/fpds/cli/fields.py @@ -21,7 +21,7 @@ @app.command() def fields( pattern: Annotated[ - Optional[str], + str | None, typer.Option( "--pattern", "-p", diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index 4156008f..564f3284 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -72,7 +72,7 @@ class fpdsRequest(fpdsMixin): max_chunk_size_mb: `int` Defaults to 100. The maximum size of each outputted data file (uncompressed). - page: `Optional[int]` + page: `int | None` Defaults to `None`. The page of results to retrieve. **kwargs: `str` @@ -101,7 +101,7 @@ def __init__( skip_regex_validation: bool = False, thread_count: int = 10, max_chunk_size_mb: int = 100, - page: Optional[int] = None, + page: int | None = None, **kwargs: str, ) -> None: self.cli_run = cli_run @@ -204,7 +204,7 @@ async def convert_with_progress( results = await asyncio.gather(*tasks) return results - def page_index(self) -> Optional[int]: + def page_index(self) -> int | None: """Converts `page` to index integer.""" idx = None if self.page: diff --git a/src/fpds/core/xml.py b/src/fpds/core/xml.py index 66ca9016..63b49c85 100644 --- a/src/fpds/core/xml.py +++ b/src/fpds/core/xml.py @@ -308,9 +308,9 @@ def get_entry_data(self) -> Dict[str, str]: def content_tag_hierarchy( self, - element: Optional[Element] = None, - parent: Optional[str] = None, - hierarchy: Optional[Dict[str, Element]] = None, + element: Element | None = None, + parent: str | None = None, + hierarchy: Dict[str, Element] | None = None, ) -> Dict[str, Element]: """Generates hierarchy within the content tag. @@ -386,7 +386,7 @@ class Parent(fpdsElement): def __init__( self, - parent_name: Optional[str] = None, + parent_name: str | None = None, **kwargs: Unpack[fpdsElementAttributes], ) -> None: super().__init__(**kwargs) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 8bc744f8..648a1769 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -37,7 +37,7 @@ def update_fields_json(dropdown_fields: List[str]) -> None: config.extend(new_options) sorted_config = sorted(config, key=lambda field: field["name"]) - with Path(FPDS_FIELDS_FILE_PATH).open(mode="w", encoding="utf-8") as file: + with Path(str(FPDS_FIELDS_FILE_PATH)).open(mode="w", encoding="utf-8") as file: json.dump(sorted_config, file, indent=4) diff --git a/src/fpds/utilities/params.py b/src/fpds/utilities/params.py index 06b2d10c..97e35ca8 100644 --- a/src/fpds/utilities/params.py +++ b/src/fpds/utilities/params.py @@ -41,7 +41,7 @@ def get_search_param_from_config( def match_regex_with_literal_string_pattern( pattern: str, string: str, -) -> Optional[re.Match[str]]: +) -> re.Match[str] | None: """Converts a regex pattern into a raw literal string to be used by Python's regex module. From e02ec83be33a9657bcc92f36a3cf00f62122db03 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 22:04:03 -0500 Subject: [PATCH 042/108] run formatters --- src/fpds/cli/__init__.py | 3 +-- src/fpds/cli/fields.py | 1 - src/fpds/cli/parse.py | 6 +++--- src/fpds/core/parser.py | 1 + src/fpds/utilities/writer.py | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/fpds/cli/__init__.py b/src/fpds/cli/__init__.py index a3aa354f..e163b146 100644 --- a/src/fpds/cli/__init__.py +++ b/src/fpds/cli/__init__.py @@ -1,4 +1,3 @@ -from fpds.cli.root import app - import fpds.cli.fields import fpds.cli.parse +from fpds.cli.root import app diff --git a/src/fpds/cli/fields.py b/src/fpds/cli/fields.py index f1864366..3c20efa9 100644 --- a/src/fpds/cli/fields.py +++ b/src/fpds/cli/fields.py @@ -8,7 +8,6 @@ import textwrap import typer -from typing import Optional from tabulate import tabulate from typing_extensions import Annotated diff --git a/src/fpds/cli/parse.py b/src/fpds/cli/parse.py index dad77ba4..e7b0a9b4 100644 --- a/src/fpds/cli/parse.py +++ b/src/fpds/cli/parse.py @@ -2,7 +2,7 @@ CLI command for retrieving FPDS federal contracts. author: derek663@gmail.com -last_updated: 2025-12-14 +last_updated: 2026-01-06 """ import asyncio @@ -43,12 +43,12 @@ def parse( output_dir.mkdir(parents=True, exist_ok=True) dir = Path(output_dir) - split_params = [param.split("=") for param in params] + split_params = [param.split("=") for param in params] # List[Tuple[str, str]] for _param in split_params: name, value = _param _param[1] = validate_kwarg(kwarg=name, string=value) params_kwargs = dict(split_params) - request = fpdsRequest(cli_run=True, **params_kwargs) # type: ignore[arg-type] + request = fpdsRequest(cli_run=True, **params_kwargs) # type: ignore[arg-type] asyncio.run(request.data(output_dir=dir)) diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index 564f3284..5554790a 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -95,6 +95,7 @@ class fpdsRequest(fpdsMixin): fpdsMissingKeywordParameterError: Raised if no keyword argument(s) are provided. """ + def __init__( self, cli_run: bool = False, diff --git a/src/fpds/utilities/writer.py b/src/fpds/utilities/writer.py index 6fba4503..f272b771 100644 --- a/src/fpds/utilities/writer.py +++ b/src/fpds/utilities/writer.py @@ -24,6 +24,7 @@ class fpdsChunkWriter: max_chunk_size_mb: `int` The maximum size of each outputted data file (uncompressed). """ + def __init__( self, output_dir: Path, @@ -58,7 +59,6 @@ async def chunkify(self, entries: AsyncGenerator[FPDS_ENTRY, None]) -> None: if chunk: self.flush(chunk) - def flush(self, chunk_buffer: list[FPDS_ENTRY]) -> None: file_path = self.output_dir / f"{uuid4()}.json.gz" with gzip.open(file_path, "wt", encoding="utf-8") as gz_file: From cd1ea7c891d399e75f7ae6fafb209de4a3601f87 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 22:05:18 -0500 Subject: [PATCH 043/108] Add PR step --- .github/workflows/pr-for-new-fields.yml | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index e9a2253f..1c5281aa 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -26,11 +26,20 @@ jobs: - name: Scrape FPDS Site run: just scrape - # - name: Installed package list - # run: apt list --installed - # - name: Remove Chrome - # run: sudo apt purge google-chrome-stable - # - name: Remove default Chromium - # run: sudo apt purge chromium-browser - # - name: Install a new Chromium - # run: sudo apt install -y chromium-browser + + - name: Set Date + run: | + echo "DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + commit-message: "minor: updating fields.json with newly identified ezSearch fields" + title: 'minor: 🤖 [AUTOMATED] new ezSearch fields identified on {{ env.DATE }}' + body: | + Automated updates from FPDS ezSearch scraper. + This PR is kept up to date automatically. + branch: minor/new-ezsearch-fields + base: master + labels: automated, feature + draft: false From 038c780a84c9f8b94b1996fea14d1ded27171f1a Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 22:07:49 -0500 Subject: [PATCH 044/108] Push formatting for tests --- tests/test_parser.py | 6 ++++-- tests/test_xml.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_parser.py b/tests/test_parser.py index b66d9fdf..8622d20c 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -4,6 +4,7 @@ from xml.etree.ElementTree import ElementTree, fromstring from fpds import fpdsRequest +from fpds.core.xml import fpdsSubTree from fpds.errors import ( fpdsDuplicateParameterConfiguration, fpdsInvalidParameter, @@ -11,7 +12,6 @@ fpdsMismatchedParameterRegexError, fpdsMissingKeywordParameterError, ) -from fpds.core.xml import fpdsSubTree from tests import FULL_RESPONSE_DATA_BYTES, NO_LINK_RESPONSE_DATA_BYTES # valid params and values @@ -65,7 +65,9 @@ def test_mb_to_bytes(self, mock_urlopen): @mock.patch("fpds.core.parser.urlopen") def test_no_pagination_links(self, mock_urlopen): """Test that initial_request correctly returns the root XML tree from the initial request.""" - mock_urlopen.return_value = MockHTTPResponse(content=NO_LINK_RESPONSE_DATA_BYTES) + mock_urlopen.return_value = MockHTTPResponse( + content=NO_LINK_RESPONSE_DATA_BYTES + ) req = fpdsRequest(**FPDS_REQUEST_PARAMS_DICT) self.assertEqual(asyncio.run(req.fetch()), []) diff --git a/tests/test_xml.py b/tests/test_xml.py index 05edd817..fe00530a 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -63,7 +63,7 @@ def setUp(self): self.element = self.xml.get_atom_feed_entries()[0] self.fpds_element = fpdsElement( element=self.element, - namespace_dict=TEST_NAMESPACE_DICT + namespace_dict=TEST_NAMESPACE_DICT, ) def test_iter(self): From 71fd013f44c4d2ed0d7ec4e432654822460d8a55 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 22:09:42 -0500 Subject: [PATCH 045/108] add formatters step in test-package GHA workflow to ensure formatter output from ruff is committed --- .github/workflows/test-package.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 0f6a10ef..a035bd95 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -34,12 +34,17 @@ jobs: - name: Install Project run: just install - - name: Run Tests - run: just test + - name: Run linters + run: just formatters - - uses: actions/checkout@v4 + - uses: stefanzweifel/git-auto-commit-action@v5 with: - fetch-depth: 0 + commit_message: "patch: [skip ci] Run formatters" + file_pattern: "*.py" + disable_globbing: true + + - name: Run Tests + run: just test test-package-version: runs-on: ubuntu-latest From c56f8f2a2b7703b8cad75b50efe07a4cf092fe99 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 22:19:42 -0500 Subject: [PATCH 046/108] Activate environment --- Justfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Justfile b/Justfile index 6ad6c1f2..958c174e 100644 --- a/Justfile +++ b/Justfile @@ -9,8 +9,9 @@ venv: ## defaults to creating virtual environment in current directory under .ve fi install: venv ## updates uv.lock if needed and manually syncs all deps + extras - uv lock - uv sync --extra all + source .venv/bin/activate \ + uv lock \ + uv sync --extra all; clean: ## Remove test and coverage artifacts rm -f .coverage From b296d7ddf4868424f0ce31d676700509ad205bcb Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 22:23:15 -0500 Subject: [PATCH 047/108] update scraper logic --- Justfile | 5 ++--- src/fpds/scripts/scraper.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Justfile b/Justfile index 958c174e..6ad6c1f2 100644 --- a/Justfile +++ b/Justfile @@ -9,9 +9,8 @@ venv: ## defaults to creating virtual environment in current directory under .ve fi install: venv ## updates uv.lock if needed and manually syncs all deps + extras - source .venv/bin/activate \ - uv lock \ - uv sync --extra all; + uv lock + uv sync --extra all clean: ## Remove test and coverage artifacts rm -f .coverage diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 648a1769..29e470a4 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -74,7 +74,7 @@ def scrape_ezsearch() -> List[str]: dropdown_fields = [] for dropdown in dropdowns: element = dropdown.find_elements(By.TAG_NAME, "option") - for opt in element: + for opt in element[1:]: # skip the first element since its the dropdown label value = opt.get_attribute("value") if value: dropdown_fields.append(value) From 02b3c41a51717771f02c67ee2480a31e6b012d60 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 22:39:10 -0500 Subject: [PATCH 048/108] Update PR title --- .github/workflows/pr-for-new-fields.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 1c5281aa..4bda8e54 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -27,18 +27,22 @@ jobs: - name: Scrape FPDS Site run: just scrape - - name: Set Date - run: | - echo "DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - - name: Create Pull Request uses: peter-evans/create-pull-request@v6 with: commit-message: "minor: updating fields.json with newly identified ezSearch fields" - title: 'minor: 🤖 [AUTOMATED] new ezSearch fields identified on {{ env.DATE }}' + title: 'minor: 🤖 [AUTOMATED] new ezSearch fields identified" body: | Automated updates from FPDS ezSearch scraper. - This PR is kept up to date automatically. + + Reference the following [data dictionary](https://www.fpds.gov/downloads/Version_1.5_specs/FPDS_DataDictionary_V1.5.pdf) + for field descriptions and regex patterns. This is a bit of a pain since + it is a manual process. + + *Checklist* + - [ ] Updated field description + - [ ] Updated field regex + branch: minor/new-ezsearch-fields base: master labels: automated, feature From ea67a7f60bed21bc05dcfddf1d12dc1f3412a51b Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 22:45:51 -0500 Subject: [PATCH 049/108] Syntax error on new fields yaml pipeline --- .github/workflows/pr-for-new-fields.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 4bda8e54..96d0b16f 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -31,7 +31,7 @@ jobs: uses: peter-evans/create-pull-request@v6 with: commit-message: "minor: updating fields.json with newly identified ezSearch fields" - title: 'minor: 🤖 [AUTOMATED] new ezSearch fields identified" + title: "minor: 🤖 [AUTOMATED] new ezSearch fields identified" body: | Automated updates from FPDS ezSearch scraper. From ed722519f9c8da86be44e48d73c5663f97e9cab4 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 22:49:09 -0500 Subject: [PATCH 050/108] Fix package dependencies and capping typing-extensions at major version 5.0.0 --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e32e422c..f9938d4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ "httpx==0.28.1", "tabulate==0.9.0", "typer==0.19.2", - "typing-extensions>=4.15.0", + "typing-extensions>=4.15.0,<5.0.0", ] [project.urls] diff --git a/uv.lock b/uv.lock index f635d588..48c3915c 100644 --- a/uv.lock +++ b/uv.lock @@ -476,7 +476,7 @@ requires-dist = [ { name = "typer", specifier = "==0.19.2" }, { name = "types-tabulate", marker = "extra == 'dev'", specifier = "==0.9.0.20241207" }, { name = "types-tqdm", marker = "extra == 'dev'", specifier = "==4.64.7.9" }, - { name = "typing-extensions", specifier = ">=4.15.0" }, + { name = "typing-extensions", specifier = ">=4.15.0,<5.0.0" }, { name = "versioningit", marker = "extra == 'dev'", specifier = ">=3.0.0,<4.0.0" }, { name = "wheel", marker = "extra == 'packaging'", specifier = "==0.37.1" }, ] From 9ec43e91b40af123425d056f93ffcc8e0b4335a5 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 22:57:33 -0500 Subject: [PATCH 051/108] Convert all classes to pascal case --- src/fpds/core/mixins.py | 2 +- src/fpds/core/parser.py | 8 ++++---- src/fpds/core/xml.py | 14 +++++++------- src/fpds/utilities/writer.py | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/fpds/core/mixins.py b/src/fpds/core/mixins.py index e3a9dfb8..e129c799 100644 --- a/src/fpds/core/mixins.py +++ b/src/fpds/core/mixins.py @@ -6,7 +6,7 @@ """ -class fpdsMixin: +class FPDSMixin: @property def url_base(self) -> str: """Base URL for all ATOM feed requests.""" diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index 5554790a..bc2652b5 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -12,7 +12,7 @@ from asyncio import Semaphore from concurrent.futures import ProcessPoolExecutor from pathlib import Path -from typing import AsyncGenerator, List, Optional, overload +from typing import AsyncGenerator, List from urllib import parse from urllib.request import urlopen from uuid import uuid4 @@ -36,10 +36,10 @@ fpdsMissingKeywordParameterError, ) from fpds.utilities import validate_kwarg -from fpds.utilities.writer import fpdsChunkWriter +from fpds.utilities.writer import FPDSChunkWriter -class fpdsRequest(fpdsMixin): +class FPDSRequest(fpdsMixin): """Makes a GET request to the FPDS ATOM feed. Takes an unlimited number of arguments. All query parameters should be @@ -275,7 +275,7 @@ async def data(self, output_dir: Path = FPDS_DATA_DATE_DIR) -> None: output_path = (Path(output_dir) / run_id).expanduser() output_path.mkdir(parents=True, exist_ok=True) - writer = fpdsChunkWriter( + writer = FPDSChunkWriter( output_dir=output_path, max_chunk_size_mb=self.max_chunk_size_mb, ) diff --git a/src/fpds/core/xml.py b/src/fpds/core/xml.py index 63b49c85..38a1d12f 100644 --- a/src/fpds/core/xml.py +++ b/src/fpds/core/xml.py @@ -6,7 +6,7 @@ """ import re -from typing import Dict, Iterator, List, Optional, TypedDict, Unpack +from typing import Dict, Iterator, List, TypedDict, Unpack from xml.etree.ElementTree import Element, ElementTree, fromstring from fpds.core import FPDS_ENTRY @@ -16,12 +16,12 @@ LAST_PAGE_REGEX = r"start=(.*?)$" -class fpdsElementAttributes(TypedDict): +class FPDSElementAttributes(TypedDict): element: Element namespace_dict: Dict[str, str] -class fpdsElement: +class FPDSElement: """Representation of a single XML element. Attributes @@ -76,7 +76,7 @@ def clean_tag(self) -> str: return clean_tag -class fpdsTree(fpdsMixin): +class FPDSTree(fpdsMixin): """Representation of initial FPDS response as an ElementTree. Attributes @@ -179,7 +179,7 @@ def jsonify(self) -> List[FPDS_ENTRY]: return json_data -class fpdsSubTree(fpdsTree): +class FPDSSubTree(FPDSTree): """A class denoting XML trees built off of the pagination links from :class:`fpdsTree`.""" pass @@ -381,13 +381,13 @@ def content_tag_hierarchy( return hierarchy -class Parent(fpdsElement): +class Parent(FPDSElement): """Representation of any XML tag containing children tags.""" def __init__( self, parent_name: str | None = None, - **kwargs: Unpack[fpdsElementAttributes], + **kwargs: Unpack[FPDSElementAttributes], ) -> None: super().__init__(**kwargs) self.parent_name = parent_name diff --git a/src/fpds/utilities/writer.py b/src/fpds/utilities/writer.py index f272b771..380835ec 100644 --- a/src/fpds/utilities/writer.py +++ b/src/fpds/utilities/writer.py @@ -14,7 +14,7 @@ from fpds.core import FPDS_ENTRY -class fpdsChunkWriter: +class FPDSChunkWriter: """Chunks FPDS request data into JSON gzip files. Attributes From c7c2e86354c22d3909af6e63175cbb35a1ead211 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 23:08:18 -0500 Subject: [PATCH 052/108] Fix casing references across repo to be pascal case --- src/fpds/__init__.py | 4 ++-- src/fpds/cli/parse.py | 4 ++-- src/fpds/core/parser.py | 32 ++++++++++++++++---------------- src/fpds/core/xml.py | 10 +++++----- src/fpds/errors.py | 12 ++++++------ src/fpds/utilities/params.py | 12 ++++++------ tests/test_cli.py | 14 -------------- tests/test_parser.py | 34 +++++++++++++++------------------- tests/test_xml.py | 14 +++++++------- 9 files changed, 59 insertions(+), 77 deletions(-) diff --git a/src/fpds/__init__.py b/src/fpds/__init__.py index 1c7310d9..d18c7602 100644 --- a/src/fpds/__init__.py +++ b/src/fpds/__init__.py @@ -1,5 +1,5 @@ -from .core.parser import fpdsRequest +from .core.parser import FPDSRequest __all__ = [ - "fpdsRequest", + "FPDSRequest", ] diff --git a/src/fpds/cli/parse.py b/src/fpds/cli/parse.py index e7b0a9b4..5cd374df 100644 --- a/src/fpds/cli/parse.py +++ b/src/fpds/cli/parse.py @@ -12,7 +12,7 @@ import typer from typing_extensions import Annotated -from fpds import fpdsRequest +from fpds import FPDSRequest from fpds.cli.root import app from fpds.config import FPDS_DATA_DATE_DIR from fpds.utilities import validate_kwarg @@ -50,5 +50,5 @@ def parse( _param[1] = validate_kwarg(kwarg=name, string=value) params_kwargs = dict(split_params) - request = fpdsRequest(cli_run=True, **params_kwargs) # type: ignore[arg-type] + request = FPDSRequest(cli_run=True, **params_kwargs) # type: ignore[arg-type] asyncio.run(request.data(output_dir=dir)) diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index bc2652b5..b1899051 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -29,17 +29,17 @@ from fpds.config import FPDS_DATA_DATE_DIR from fpds.core import FPDS_ENTRY -from fpds.core.mixins import fpdsMixin -from fpds.core.xml import fpdsSubTree, fpdsTree +from fpds.core.mixins import FPDSMixin +from fpds.core.xml import FPDSSubTree, FPDSTree from fpds.errors import ( - fpdsMaxPageLengthExceededError, - fpdsMissingKeywordParameterError, + FPDSMaxPageLengthExceededError, + FPDSMissingKeywordParameterError, ) from fpds.utilities import validate_kwarg from fpds.utilities.writer import FPDSChunkWriter -class FPDSRequest(fpdsMixin): +class FPDSRequest(FPDSMixin): """Makes a GET request to the FPDS ATOM feed. Takes an unlimited number of arguments. All query parameters should be @@ -86,13 +86,13 @@ class FPDSRequest(fpdsMixin): fpdsInvalidParameter: Raised if an invalid parameter is provided. - fpdsMaxPageLengthExceededError: + FPDSMaxPageLengthExceededError: Raised if user requests a page of results that doesn't exist. fpdsMismatchedParameterRegexError: Raised if parameter value does not match expected regex pattern. - fpdsMissingKeywordParameterError: + FPDSMissingKeywordParameterError: Raised if no keyword argument(s) are provided. """ @@ -115,9 +115,9 @@ def __init__( if kwargs: self.kwargs = kwargs else: - raise fpdsMissingKeywordParameterError + raise FPDSMissingKeywordParameterError - tree = fpdsTree(content=self.initial_request()) + tree = FPDSTree(content=self.initial_request()) links = tree.pagination_links(params=self.search_params) self.links = links @@ -125,7 +125,7 @@ def __init__( idx = self.page_index() if idx is not None and self.links: if self.page > self.page_count: - raise fpdsMaxPageLengthExceededError(page_count=self.page_count) + raise FPDSMaxPageLengthExceededError(page_count=self.page_count) self.links = [links[idx]] # do not run class validations since CLI command has its own @@ -171,14 +171,14 @@ async def convert( client: AsyncClient, link: str, semaphore: Semaphore, - ) -> fpdsSubTree: + ) -> FPDSSubTree: """Retrieves content from FPDS ATOM feed as a SubTree instance.""" async with semaphore: response = await client.get(link) - subtree = fpdsSubTree(content=response.content) + subtree = FPDSSubTree(content=response.content) return subtree - async def fetch(self) -> List[fpdsSubTree]: + async def fetch(self) -> List[FPDSSubTree]: """Asynchronously parses all ATOM feed pages for current request.""" if not self.links: return [] @@ -190,7 +190,7 @@ async def convert_with_progress( semaphore: asyncio.Semaphore, progress: Progress, task_id: TaskID, - ) -> fpdsSubTree: + ) -> FPDSSubTree: result = await self.convert(client=client, link=link, semaphore=semaphore) progress.update(task_id=task_id, advance=1) return result @@ -225,7 +225,7 @@ def _create_progress() -> Progress: ) @staticmethod - def _jsonify(entry: fpdsSubTree) -> List[FPDS_ENTRY]: + def _jsonify(entry: FPDSSubTree) -> List[FPDS_ENTRY]: """Wrapper around `jsonify` method for avoiding pickle issue.""" return entry.jsonify() @@ -247,7 +247,7 @@ async def iter_data(self) -> AsyncGenerator[FPDS_ENTRY, None]: from concurrent.futures import as_completed num_processes = multiprocessing.cpu_count() - data = await self.fetch() # List[fpdsSubTree] + data = await self.fetch() # List[FPDSSubTree] with ProcessPoolExecutor(max_workers=num_processes) as pool: with self._create_progress() as progress: diff --git a/src/fpds/core/xml.py b/src/fpds/core/xml.py index 38a1d12f..9b9382ea 100644 --- a/src/fpds/core/xml.py +++ b/src/fpds/core/xml.py @@ -10,7 +10,7 @@ from xml.etree.ElementTree import Element, ElementTree, fromstring from fpds.core import FPDS_ENTRY -from fpds.core.mixins import fpdsMixin +from fpds.core.mixins import FPDSMixin NAMESPACE_REGEX = r"\{(.*)\}" LAST_PAGE_REGEX = r"start=(.*?)$" @@ -46,7 +46,7 @@ def __getitem__(self, index: int) -> Element: return self.element[index] def __str__(self) -> str: # pragma: no cover - return f"" + return f"" def parse_items(self) -> Iterator[Element]: """Returns iteration of `Element` as a generator.""" @@ -76,7 +76,7 @@ def clean_tag(self) -> str: return clean_tag -class FPDSTree(fpdsMixin): +class FPDSTree(FPDSMixin): """Representation of initial FPDS response as an ElementTree. Attributes @@ -241,7 +241,7 @@ def _generate_nested_attribute_dict(self) -> Dict[str, str]: return _attributes_copy -class Entry(fpdsElement): +class Entry(FPDSElement): """An ATOM feed data entry. In terms of XML, it is the outermost container for award data. @@ -270,7 +270,7 @@ class Entry(fpdsElement): """ - def __init__(self, **kwargs: Unpack[fpdsElementAttributes]) -> None: + def __init__(self, **kwargs: Unpack[FPDSElementAttributes]) -> None: super().__init__(**kwargs) def __str__(self) -> str: # pragma: no cover diff --git a/src/fpds/errors.py b/src/fpds/errors.py index 0e468bba..5f0df331 100644 --- a/src/fpds/errors.py +++ b/src/fpds/errors.py @@ -1,31 +1,31 @@ -"""Errors for fpds.""" +"""Errors for FPDS.""" -class fpdsMaxPageLengthExceededError(Exception): +class FPDSMaxPageLengthExceededError(Exception): def __init__(self, page_count: int) -> None: self.message = f"Maximum response page count is {page_count}" super().__init__(self.message) -class fpdsMissingKeywordParameterError(Exception): +class FPDSMissingKeywordParameterError(Exception): def __init__(self) -> None: self.message = "You must provide at least one keyword parameter" super().__init__(self.message) -class fpdsMismatchedParameterRegexError(Exception): +class FPDSMismatchedParameterRegexError(Exception): def __init__(self, string: str, pattern: str) -> None: self.message = f"`{string}` does not match regex: {pattern}" super().__init__(self.message) -class fpdsInvalidParameter(Exception): +class FPDSInvalidParameter(Exception): def __init__(self, name: str) -> None: self.message = f"`{name}` is not a valid FPDS parameter" super().__init__(self.message) -class fpdsDuplicateParameterConfiguration(Exception): +class FPDSDuplicateParameterConfiguration(Exception): def __init__(self, name: str) -> None: self.message = f"Multiple records for parameter `{name}` found in config!" super().__init__(self.message) diff --git a/src/fpds/utilities/params.py b/src/fpds/utilities/params.py index 97e35ca8..33ed431d 100644 --- a/src/fpds/utilities/params.py +++ b/src/fpds/utilities/params.py @@ -10,9 +10,9 @@ from fpds.config import FPDS_FIELDS_CONFIG as FIELDS from fpds.errors import ( - fpdsDuplicateParameterConfiguration, - fpdsInvalidParameter, - fpdsMismatchedParameterRegexError, + FPDSDuplicateParameterConfiguration, + FPDSInvalidParameter, + FPDSMismatchedParameterRegexError, ) CONFIG_TYPE = List[Dict[str, Any]] @@ -32,9 +32,9 @@ def get_search_param_from_config( """Finds the name of a kwarg in `fields.json`.""" field_config = [field for field in config if field.get("name") == name] if not field_config: - raise fpdsInvalidParameter(name=name) + raise FPDSInvalidParameter(name=name) elif len(field_config) > 1: - raise fpdsDuplicateParameterConfiguration(name=name) + raise FPDSDuplicateParameterConfiguration(name=name) return cast(ParameterConfig, field_config[0]) @@ -61,7 +61,7 @@ def validate_kwarg(kwarg: str, string: str) -> str: pattern = obj["regex"] match = match_regex_with_literal_string_pattern(pattern=pattern, string=string) if not match: - raise fpdsMismatchedParameterRegexError(string=string, pattern=pattern) + raise FPDSMismatchedParameterRegexError(string=string, pattern=pattern) if obj.get("quotes"): return f'"{string}"' diff --git a/tests/test_cli.py b/tests/test_cli.py index 2e28bae3..dd1dd030 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -24,19 +24,5 @@ def test_invalid_cli_parameter_regex_pattern(self): result = self.runner.invoke(app, ["parse", "AGENCY_CODE={not-valid}"]) self.assertIn("does not match regex", result.__str__()) - # def test_parse_with_custom_output_dir(self): - # with self.runner.isolated_filesystem(): - # result = self.runner.invoke( - # app, - # [ - # "parse", - # "AGENCY_CODE=7504", - # "-o", - # "./test", - # ], - # ) - # self.assertEqual(result.exit_code, 0) - - if __name__ == "__main__": unittest.main() diff --git a/tests/test_parser.py b/tests/test_parser.py index 8622d20c..ae9eaa13 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -3,14 +3,10 @@ from unittest import TestCase, mock from xml.etree.ElementTree import ElementTree, fromstring -from fpds import fpdsRequest -from fpds.core.xml import fpdsSubTree +from fpds import FPDSRequest from fpds.errors import ( - fpdsDuplicateParameterConfiguration, - fpdsInvalidParameter, - fpdsMaxPageLengthExceededError, - fpdsMismatchedParameterRegexError, - fpdsMissingKeywordParameterError, + FPDSMaxPageLengthExceededError, + FPDSMissingKeywordParameterError, ) from tests import FULL_RESPONSE_DATA_BYTES, NO_LINK_RESPONSE_DATA_BYTES @@ -50,16 +46,16 @@ def __exit__(self, exc_type, exc_value, traceback): pass -class TestFpdsRequest(TestCase): +class TestFPDSRequest(TestCase): def test_parameter_error_raised_with_no_kwargs(self): - with self.assertRaises(fpdsMissingKeywordParameterError): - fpdsRequest({}) + with self.assertRaises(FPDSMissingKeywordParameterError): + FPDSRequest({}) @mock.patch("fpds.core.parser.urlopen") def test_mb_to_bytes(self, mock_urlopen): """Test that mb_to_bytes correctly converts megabytes to bytes.""" mock_urlopen.return_value = MockHTTPResponse() - req = fpdsRequest(**FPDS_REQUEST_PARAMS_DICT, max_chunk_size_mb=50) + req = FPDSRequest(**FPDS_REQUEST_PARAMS_DICT, max_chunk_size_mb=50) self.assertEqual(req.mb_to_bytes(), 50 * 1_048_576) @mock.patch("fpds.core.parser.urlopen") @@ -68,14 +64,14 @@ def test_no_pagination_links(self, mock_urlopen): mock_urlopen.return_value = MockHTTPResponse( content=NO_LINK_RESPONSE_DATA_BYTES ) - req = fpdsRequest(**FPDS_REQUEST_PARAMS_DICT) + req = FPDSRequest(**FPDS_REQUEST_PARAMS_DICT) self.assertEqual(asyncio.run(req.fetch()), []) # @mock.patch("fpds.core.parser.urlopen") # def test_convert(self, mock_urlopen): # """Test that initial_request correctly returns the root XML tree from the initial request.""" # mock_urlopen.return_value = MockHTTPResponse() - # req = fpdsRequest(**FPDS_REQUEST_PARAMS_DICT) + # req = FPDSRequest(**FPDS_REQUEST_PARAMS_DICT) # mock_client = mock.AsyncMock() # mock_client.get.return_value = MockHTTPResponse(content=FULL_RESPONSE_DATA_BYTES) @@ -96,15 +92,15 @@ def test_no_pagination_links(self, mock_urlopen): def test_request_link_count(self, mock_urlopen): """Test that generated number of links from initial request is correct.""" mock_urlopen.return_value = MockHTTPResponse() - req = fpdsRequest(**FPDS_REQUEST_PARAMS_DICT) + req = FPDSRequest(**FPDS_REQUEST_PARAMS_DICT) self.assertEqual(len(req.links), 3) - @mock.patch("fpds.core.parser.fpdsRequest.page_index") + @mock.patch("fpds.core.parser.FPDSRequest.page_index") @mock.patch("fpds.core.parser.urlopen") def test_request_link_count(self, mock_urlopen, mock_page_index): """Test that page_index is called once when page is provided.""" mock_urlopen.return_value = MockHTTPResponse() - fpdsRequest( + FPDSRequest( **FPDS_REQUEST_PARAMS_DICT, page=1, ) @@ -113,8 +109,8 @@ def test_request_link_count(self, mock_urlopen, mock_page_index): @mock.patch("fpds.core.parser.urlopen") def test_max_page_length_exceeded_error_raised(self, mock_urlopen): mock_urlopen.return_value = MockHTTPResponse() - with self.assertRaises(fpdsMaxPageLengthExceededError): - fpdsRequest( + with self.assertRaises(FPDSMaxPageLengthExceededError): + FPDSRequest( **FPDS_REQUEST_PARAMS_DICT, page=1_000_000, ) @@ -123,7 +119,7 @@ def test_max_page_length_exceeded_error_raised(self, mock_urlopen): def test_skip_regex_validation_warning_raised(self, mock_urlopen): mock_urlopen.return_value = MockHTTPResponse() with self.assertWarns(UserWarning): - fpdsRequest( + FPDSRequest( **FPDS_REQUEST_PARAMS_DICT, skip_regex_validation=True, ) diff --git a/tests/test_xml.py b/tests/test_xml.py index fe00530a..79207a24 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -1,7 +1,7 @@ from unittest import TestCase from xml.etree.ElementTree import ElementTree -from fpds.core.xml import fpdsElement, fpdsTree +from fpds.core.xml import FPDSElement, FPDSTree from tests import FULL_RESPONSE_DATA_BYTES, TRUNCATED_RESPONSE_DATA_BYTES FPDS_REQUEST_PARAMS_DICT = { @@ -14,9 +14,9 @@ } -class TestFpdsTree(TestCase): +class TestFPDSTree(TestCase): def setUp(self): - self._class = fpdsTree(FULL_RESPONSE_DATA_BYTES) + self._class = FPDSTree(FULL_RESPONSE_DATA_BYTES) def test_convert_to_lxml_tree(self): content = self._class.convert_to_lxml_tree() @@ -38,7 +38,7 @@ def test_lower_limit_count_truncated_response(self): ensures that if the response size is less than 10 that the `lower_limit` property is still generated correctly. """ - _class = fpdsTree(TRUNCATED_RESPONSE_DATA_BYTES) + _class = FPDSTree(TRUNCATED_RESPONSE_DATA_BYTES) total = _class.lower_limit self.assertEqual(total, 1) @@ -57,11 +57,11 @@ def test_jsonify(self): self.assertEqual(len(entries), 10) -class TestFpdsElement(TestCase): +class TestFPDSElement(TestCase): def setUp(self): - self.xml = fpdsTree(content=FULL_RESPONSE_DATA_BYTES) + self.xml = FPDSTree(content=FULL_RESPONSE_DATA_BYTES) self.element = self.xml.get_atom_feed_entries()[0] - self.fpds_element = fpdsElement( + self.fpds_element = FPDSElement( element=self.element, namespace_dict=TEST_NAMESPACE_DICT, ) From e4d882d0fa2e0e270864c92aa37ba0eb9f63ac1f Mon Sep 17 00:00:00 2001 From: dherincx92 <37759088+dherincx92@users.noreply.github.com> Date: Wed, 7 Jan 2026 04:08:40 +0000 Subject: [PATCH 053/108] patch: [skip ci] Run formatters --- tests/test_cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index dd1dd030..a72da336 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -24,5 +24,6 @@ def test_invalid_cli_parameter_regex_pattern(self): result = self.runner.invoke(app, ["parse", "AGENCY_CODE={not-valid}"]) self.assertIn("does not match regex", result.__str__()) + if __name__ == "__main__": unittest.main() From 7fb3e86318879840794b1292472db865466d9573 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 23:18:32 -0500 Subject: [PATCH 054/108] Add additional configs to auto commit --- .github/workflows/test-package.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index a035bd95..77defdef 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -42,6 +42,8 @@ jobs: commit_message: "patch: [skip ci] Run formatters" file_pattern: "*.py" disable_globbing: true + add: true + push: true - name: Run Tests run: just test From 6ed09272add4d4980a5e5c88ddde262e9e92fbfb Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 23:19:47 -0500 Subject: [PATCH 055/108] Remove bad options --- .github/workflows/test-package.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 77defdef..a035bd95 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -42,8 +42,6 @@ jobs: commit_message: "patch: [skip ci] Run formatters" file_pattern: "*.py" disable_globbing: true - add: true - push: true - name: Run Tests run: just test From 4aeaafc7dc406b0f5a9c11ae717ee3b700239e1c Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 23:22:23 -0500 Subject: [PATCH 056/108] Add trailing whitespace when writing new fields.json file --- src/fpds/scripts/scraper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 29e470a4..5d7f1009 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -26,10 +26,10 @@ def update_fields_json(dropdown_fields: List[str]) -> None: # as of right now, we have no way to validate the pattern unless we go to the data dict new_options = [ { - "description": "", + "description": "", "name": field, "quotes": False, - "regex": "", + "regex": "", } for field in dropdown_fields if field not in current_field_options @@ -39,6 +39,7 @@ def update_fields_json(dropdown_fields: List[str]) -> None: with Path(str(FPDS_FIELDS_FILE_PATH)).open(mode="w", encoding="utf-8") as file: json.dump(sorted_config, file, indent=4) + file.write("\n") def scrape_ezsearch() -> List[str]: From dfe04a4d4755e791f683cc9824fe84fe0636c192 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 23:24:44 -0500 Subject: [PATCH 057/108] comment pull request action --- .github/workflows/pr-for-new-fields.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 96d0b16f..967c6b1b 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -1,7 +1,9 @@ name: New fpds search fields PR on: - pull_request: + schedule: + - cron: "0 0 * * *" + # pull_request: # TODO: fully remove once done testing workflow_dispatch: jobs: @@ -31,7 +33,7 @@ jobs: uses: peter-evans/create-pull-request@v6 with: commit-message: "minor: updating fields.json with newly identified ezSearch fields" - title: "minor: 🤖 [AUTOMATED] new ezSearch fields identified" + title: "minor: 🤖 [AUTOMATED] new ezSearch fields" body: | Automated updates from FPDS ezSearch scraper. @@ -39,7 +41,7 @@ jobs: for field descriptions and regex patterns. This is a bit of a pain since it is a manual process. - *Checklist* + **Checklist** - [ ] Updated field description - [ ] Updated field regex From 92d9185606818f0c3366e504fccee197b9b995db Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Tue, 6 Jan 2026 23:30:47 -0500 Subject: [PATCH 058/108] Fix parse to ensure dir var always exists --- src/fpds/cli/parse.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/fpds/cli/parse.py b/src/fpds/cli/parse.py index 5cd374df..56983913 100644 --- a/src/fpds/cli/parse.py +++ b/src/fpds/cli/parse.py @@ -38,10 +38,8 @@ def parse( ) -> None: """Sends ATOM feed request to FPDS.""" - if output_dir: - if not output_dir.exists(): - output_dir.mkdir(parents=True, exist_ok=True) - dir = Path(output_dir) + output_dir_path: Path = Path(output_dir) + output_dir_path.mkdir(parents=True, exist_ok=True) split_params = [param.split("=") for param in params] # List[Tuple[str, str]] @@ -51,4 +49,4 @@ def parse( params_kwargs = dict(split_params) request = FPDSRequest(cli_run=True, **params_kwargs) # type: ignore[arg-type] - asyncio.run(request.data(output_dir=dir)) + asyncio.run(request.data(output_dir=output_dir_path)) From 0df4e8a0641fab7eb292f357af5749f3ecb99285 Mon Sep 17 00:00:00 2001 From: "Derek Herincx, M.S." <37759088+dherincx92@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:52:20 -0500 Subject: [PATCH 059/108] Update Justfile Co-authored-by: Leon Kozlowski --- Justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Justfile b/Justfile index 6ad6c1f2..a6f080ce 100644 --- a/Justfile +++ b/Justfile @@ -39,4 +39,4 @@ publish: package ## publishes package to pypi uv publish scrape: venv install - uv run python src/fpds/scripts/scraper.py \ No newline at end of file + uv run python src/fpds/scripts/scraper.py From 5d4d33233177328569b7412170365a3542117f29 Mon Sep 17 00:00:00 2001 From: "Derek Herincx, M.S." <37759088+dherincx92@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:52:31 -0500 Subject: [PATCH 060/108] Update src/fpds/utilities/writer.py Co-authored-by: Leon Kozlowski --- src/fpds/utilities/writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpds/utilities/writer.py b/src/fpds/utilities/writer.py index 380835ec..8f2cff7f 100644 --- a/src/fpds/utilities/writer.py +++ b/src/fpds/utilities/writer.py @@ -8,7 +8,7 @@ import gzip import json from pathlib import Path -from typing import AsyncGenerator, List +from typing import AsyncGenerator from uuid import uuid4 from fpds.core import FPDS_ENTRY From 0d58d3d883c7393260576a419aac6f41fcdc644d Mon Sep 17 00:00:00 2001 From: "Derek Herincx, M.S." <37759088+dherincx92@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:52:39 -0500 Subject: [PATCH 061/108] Update src/fpds/utilities/writer.py Co-authored-by: Leon Kozlowski --- src/fpds/utilities/writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpds/utilities/writer.py b/src/fpds/utilities/writer.py index 8f2cff7f..aa6557bf 100644 --- a/src/fpds/utilities/writer.py +++ b/src/fpds/utilities/writer.py @@ -42,7 +42,7 @@ def record_size(entry: FPDS_ENTRY) -> int: async def chunkify(self, entries: AsyncGenerator[FPDS_ENTRY, None]) -> None: """Chunkifies FPDS entries into JSON gzip files of :max_chunk_size_mb: size.""" - chunk: List[FPDS_ENTRY] = [] + chunk: list[FPDS_ENTRY] = [] current_size = 0 async for entry in entries: From 15e410e91368af22b9619347b921b53ee3c5c576 Mon Sep 17 00:00:00 2001 From: "Derek Herincx, M.S." <37759088+dherincx92@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:52:48 -0500 Subject: [PATCH 062/108] Update README.md Co-authored-by: Leon Kozlowski --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6bb779e..8970a518 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ $ make clean Run unit tests on your local environment: ``` -$ make local-test +$ just test ``` ## Usage From 3abcb5fed5638db01b4e89f8f0387ca6fb77e3bf Mon Sep 17 00:00:00 2001 From: Mitch Bregman Date: Tue, 6 Jan 2026 22:59:12 -0500 Subject: [PATCH 063/108] fix indent in pyproject.toml --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f9938d4f..bc424d53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,9 +39,9 @@ dev = [ "versioningit>=3.0.0,<4.0.0", ] tests = [ - "pytest==7.1.3", - "pytest-cov==3.0.0", - "pytest-runner==6.0.0", + "pytest==7.1.3", + "pytest-cov==3.0.0", + "pytest-runner==6.0.0", ] packaging = [ "build==0.8.0", From 750adde8e25eaecb7a4f8f4369ed4659e214ea61 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Thu, 8 Jan 2026 23:25:45 -0500 Subject: [PATCH 064/108] Remove PR template markdown --- .github/pull_request_template.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index e69de29b..00000000 From dcb4bf1237d42bb1206e7f0e7f592cd25c481645 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 16:05:40 -0500 Subject: [PATCH 065/108] minor: update README --- README.md | 121 ++++++++++++++++++++++++++++++++++++----- src/fpds/cli/fields.py | 2 +- 2 files changed, 108 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8970a518..30d134ba 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@
- __________ ____ _____ - / ____/ __ \/ __ \/ ___/ - / /_ / /_/ / / / /\__ \ - / __/ / ____/ /_/ /___/ / - /_/ /_/ /_____//____/ + __________ ____ _____ + / ____/ __ \/ __ \/ ___/ + / /_ / /_/ / / / /\__ \ + / __/ / ____/ /_/ /___/ / + /_/ /_/ /_____//____/ - Welcome to a more user-friendly FPDS 🚀 + Welcome to a more user-friendly FPDS 🚀 +
A light-weight, pythonic parser for the Federal Procurement Data System (FPDS) ATOM Feed. Reference [here](https://www.fpds.gov/fpdsng_cms/index.php/en/). - ## Motivation @@ -21,24 +21,117 @@ This library helps users by doing the following: - Converting XML and all associated attributes into JSON format -## Setup +## Prerequisites As of version 1.5.0, this library manages dependencies using `uv`. It is _highly_ recommended since this library is tested with it. Note that this README assumes you will install `uv` and therefore runs all commands within its context. -### Installing `uv` - +### `uv` You can follow any of the methods found [here](https://docs.astral.sh/uv/getting-started/installation/). -If on Linux or MacOS, we recommend using Homebrew: - +If on MacOS, we recommend using Homebrew: ``` $ brew install uv ``` +### `just` +A command runner inspired by `Makefile`, written in Rust. +``` +$ brew install just +``` + Once `uv` is installed, you can use the project Makefile to ensure your local environment -is synced with the latest library installation. Start by running `make install` — this -will check the status of the `uv.lock` file, and install all project dependencies + extras. +is synced with the latest library installation. Start by running `just install` — this +will check the status of the `uv.lock` file, and install all project dependencies + +package extras. + +## Usage +For a list of valid search criteria parameters, consult FPDS documentation +found [here](https://www.fpds.gov/wiki/index.php/Atom_Feed_Usage). + +### CLI +### `fields` +Returns fields available for API requests. + +To display all available fields: + +``` +$ uv run fpds fields +``` + +If successful, you should see a nice, tabulated table in your terminal + +![fields-cli-output](img/fpds-fields-cli-output.png) +
Figure 1 - Tabulated fields CLI output
+
+ + +If you wanted to perform a more targeted search, add `-p`. For example, to +get all fields containing the text "vendor" anywhere in the name, you could +run the following: +``` +$ uv run fpds fields -p vendor +``` + +![fields-cli-vendor](img/fpds-fields-vendor.png) +
Figure 2 - Matching "vendor" fields
+
+ +### `parse` +Sends and parses records from an FPDS ATOM feed request + +Lets say you wanted records from the OFFICE OF THE INSPECTOR GENERAL. +Through your research, you identified that agency's code as 7504. For your +particular project, you are only interested in awards modified within the first +quarter of 2025. Using the `parse` command, you can easily retrieve records with +the following command: + +``` +$ uv run fpds parse "LAST_MOD_DATE=[2022/01/01, 2022/03/31]" "AGENCY_CODE=7504" +``` + +With this command, you can specify as many filters as you want. Unfortunately due +to rate limitations with the ATOM feed, you _must_ have at least 1 filter. + +By default, this command will output records to a directory named `.fpds` in +your home directory. If you wish to output to a different location, specify your +location with `-o`: + +``` +$ uv run fpds parse "LAST_MOD_DATE=[2022/01/01, 2022/03/31]" "AGENCY_CODE=7504" -o /Users/Desktop/data +``` + +## Python +Core parsing and transformation classes are exposed as first-class citizens. + +```{python} + +import asyncio +from fpds import FPDSRequest + +request = FPDSRequest( + LAST_MOD_DATE="[2022/01/01, 2022/03/31]", + AGENCY_CODE="7504" +) + +# will return the initial HTTP request a user would make if using Postman +request_url = request.__url__() + +# total number of pages in request +page_count = request.page_count + + +# returns records as an async generator +gen = request.iter_data() + +# evaluating generator entries +records = [] +async for entry in gen: + records.append(entry) + +# or letting `data` method evaluate generator for you +records = asyncio.run(request.data()) +``` ### Local Development diff --git a/src/fpds/cli/fields.py b/src/fpds/cli/fields.py index 3c20efa9..6fcc55a2 100644 --- a/src/fpds/cli/fields.py +++ b/src/fpds/cli/fields.py @@ -59,7 +59,7 @@ def text_wrap(text: str, width: int = width) -> str: data.append( [ field["name"], - text_wrap(field["description"], width=20), + text_wrap(field["description"], width=width), text_wrap(field["regex"], width=width), ] ) From a6d22b0ad5c23b2b0a9fa8ac792706ad7ddf0619 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 16:11:50 -0500 Subject: [PATCH 066/108] Attempt to fix ascii --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 30d134ba..a98667cc 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@
+ __________ ____ _____ + / ____/ __ \/ __ \/ ___/ + / /_ / /_/ / / / /\__ \ + / __/ / ____/ /_/ /___/ / +/_/ /_/ /_____//____/ - - __________ ____ _____ - / ____/ __ \/ __ \/ ___/ - / /_ / /_/ / / / /\__ \ - / __/ / ____/ /_/ /___/ / - /_/ /_/ /_____//____/ - - Welcome to a more user-friendly FPDS 🚀 +Welcome to a more user-friendly FPDS 🚀
A light-weight, pythonic parser for the Federal Procurement Data System (FPDS) ATOM Feed. Reference [here](https://www.fpds.gov/fpdsng_cms/index.php/en/). From dae30b617f17a5ca077b9061cc136018defec635 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 16:14:47 -0500 Subject: [PATCH 067/108] Add pre tag --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a98667cc..d26092da 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@
+
     __________  ____  _____
    / ____/ __ \/ __ \/ ___/
   / /_  / /_/ / / / /\__ \
@@ -6,6 +7,7 @@
 /_/   /_/   /_____//____/
 
 Welcome to a more user-friendly FPDS 🚀
+
A light-weight, pythonic parser for the Federal Procurement Data System (FPDS) ATOM Feed. Reference [here](https://www.fpds.gov/fpdsng_cms/index.php/en/). From 39d6c3e24c6751b77cbeb9af48766b5991b4f1a2 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 16:28:14 -0500 Subject: [PATCH 068/108] major: closing in on the final commit --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index d26092da..de3b61e4 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,10 @@ / /_ / /_/ / / / /\__ \ / __/ / ____/ /_/ /___/ / /_/ /_/ /_____//____/ - Welcome to a more user-friendly FPDS 🚀 A light-weight, pythonic parser for the Federal Procurement Data System (FPDS) ATOM Feed. -Reference [here](https://www.fpds.gov/fpdsng_cms/index.php/en/). ## Motivation From 260c79a45a84d494bb3a23027794d42c154573bf Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 16:29:21 -0500 Subject: [PATCH 069/108] typo removing ref to Makefile --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de3b61e4..e670256d 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ A command runner inspired by `Makefile`, written in Rust. $ brew install just ``` -Once `uv` is installed, you can use the project Makefile to ensure your local environment +Once `uv` is installed, you can use the project Justfile to ensure your local environment is synced with the latest library installation. Start by running `just install` — this will check the status of the `uv.lock` file, and install all project dependencies + package extras. From 65f7f4befcc653b11b098cc5889f145bac124419 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 16:30:27 -0500 Subject: [PATCH 070/108] Add img/ dir --- img/fpds-fields-cli-output.png | Bin 0 -> 140607 bytes img/fpds-fields-vendor.png | Bin 0 -> 110368 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 img/fpds-fields-cli-output.png create mode 100644 img/fpds-fields-vendor.png diff --git a/img/fpds-fields-cli-output.png b/img/fpds-fields-cli-output.png new file mode 100644 index 0000000000000000000000000000000000000000..4f25eee9d60249d4c410cfc17e650611dece4d8a GIT binary patch literal 140607 zcmd?RbyQVRv^Po$A}JjLq5{%LcXvul3(|0q?v|2Px&`T$ZfWW6lJ4$)`*4o;-uJ%m z-uK@d;~UQbp4e-zJ=dCZ%{AxmHy6Q*@)Bq$L?|#YFlbVeqDn9@h@mhraGl6cfN$1& z$Ms-fP-V?TL=>e&M939wt&PnrjbLCTgJaZ@G?dke`JFxqBVz`6hnB{XQ@rvH#Z-ft z)>09EP992t?faGZZIS*wg)erIFSSOIHx9nJ4o*j72s~~Uo`ntV!&BIbQ?kJazQNjy z#)GQ?x2A*ojc|CF#t_Ds^^8QARjFem=9c@ypQ|`{TZDozu!`O<$d3?h-%`=gkO&g< zKg{du`7I1F@K9IYPe16%$xwfL1|yh3E|1@X=z{rx0JA_7*>em_F8$J)pH?rw-Qwj$ zR13_@;`ht4xFFSKd)y|@O>F!&|4S^GPY(Qgu}{6l$yPrg`pV{t1Wv23n;r$`HebFF>!NGrqbQcq!kcAwqewE1%$Av$E%Jeez z^D& zzWby-fcX&3NE|AZ_wXSKd+`01;mik$g-HH&jdrrF8cfxqQVFYJ?=oZq#o>I%UI@R3wlhna6Te zx?v+;4#0mT$JeEO67%{M)@yHxNUVof6#SNO@gfYCJ@;ON80C5d=$~2x2|5l#>8?wu zm?H@UQC~j6va*4nL%L{fTN=h-H%Sh2L)|#)NIQ%ll5!TTk=~@3OJ-pB7WUQl-KOug zkjRS9pw4s$y7bAGN!M30;70D zxFskD`g%-k{^Ex7x7k*92qf+ z9R0PQ8Phdg&47_UUOW<>-*d$$YQ7HX&n;nRh0vy+o%>M^p@jSC{dW0`rHxp9SKjh`G@SlAR9epo^lQ>LBNMtmv=<9X1RSIm;{z6E@ucrNPGClX6;^vC??^XY&w z3IU41$dx}Ieqz@K&q=sO3PfE{2jpjR=VF=rhf7RTBcX|`4CR?pa%0nrh77NnB5H>1 z3lR^mY`UD%%=q(5+%S9ReU$CPvHLwmfFIJXREjDaK>u61%(jrIpiA?wM^mW7$Hpn1 z6HwpOHx;o*gD!!nqUb22cJCyOg?p=Sg)D|^=r3p9B;q1 zQ>}fMmL!&Rla!E(lbruq{fX_@)9C~fQTdlLf5cX8k3q*+$8V0YkCDDoc6?iw3jT@w z){dH%nunS``dKun2Pq$KJaJntEYnySUzs-dY)pBKXH4DP+}zll)O^_7pi;~n*ZgK| zAa`EfEvL8$lrx~JpsJqBsJ@uxDSMZFO-mGJEOwHaHFRv&YgSo-UEx+yO@xXjftDJe zX!t`sMm)3gO&GpC%KW#19}1cHBfVQloA{fJi8yk!a>bN-c0R^2>o5Cv67Muoi=Jvm zX}W0kRybOujJxK`jZ}@GkKvi)TUbx>|G10vVeTW;4)8AfQs!s#b+|}mtZZUw>(fs3 z*6#S$(AMbqgu}4hP)d9ipAU3DLw4IN z2A=?5ly07`--2r6#WmfX;2p`G>b2ap6WV8Z9zRpR4^NWNl}o^t<_Y@=|1<26ZoCt^ zZ932gCA#U@53xJ+3R+ENzpA7wt|#)lxAQ7f*#_%|>ISaE(fT_ljkDz{`*F20=x5ox z9KY|0@r(_OGxQes?ECVW9~d)MEq+?$3TGNHO#Y;OR7swrW6E55Jxr?HU zvI~EOzB4COkL~{Sa!|flzE++m8Kf|jipl-MRJiwxK+0Y+;(+RaI^_uE6FF_pGM8l; zd^h~4UgJ38V${j*;>EhET2EU$r|-4n^=CC&H957`_RRwmA0^7-$_AY=d2jjfTq|4~ zPV7(mF6Iud0^|JD!zV!{Io}JZf+_dnPlsmvk`5aC>w5#&zOR>$=k!jF#T{tv-TWS$ zQ`oE?J$Tv``@Mg<&1LCemGMMi^C4iTJ7tg$&l?c|Df)>$f;mzrlG>AnXYZc%pyof@ ze&&dlO6*Se1@k%HFPc^E!62%?R>g{g8;Ffwu(51!m>Srt*sGqd#6M*|F_7hpc~G7uK9*J*-boWOjna)FL8^@ZoZHIL%d}UNt8ki$HvZYvO7oruw~+oJGeC8eBbKw z=f*GMi`cZ@)q8}`CuiBE;yl57`)L~dp=6PHN4lynxjpwD@A&0{oqBp<3?JT-_)UWZ}@*Fw6kfCCj zFFm!(D?QatQ2yXD-DTu!w9%Ay=+SWj-^&u~RQOq|%;k+u)~B^f)usY-HH#t!?YqzY zt88HwA1q`h?<+yqb>(XeYg!Y^le7~~s$bMutxKm$ZQHtf3VRAv?^V%=(Ks09FO9!{ z9?k0aOxmd?uzEXt>{;Nv6K2&iOH@#l-&>7cjc@7I@akjN7TJ`**gosjhpLlNhVJ6x z#Q4PTmHU;I^=M8dxgt9_4t#D(7us{grPZ|yKRmVI%YRssP325o>~>XTLBB8SqCE8#;XES1RV58!2B1PdFn^+#VJv8Y`bE-_NQhPDM*RYIyCV3R) zlj$#U6Co9GMmorQUuxw#yN4}BRh%5kKW#&^JC>m5>&a!4Xm`Cyc)(I*!Z%3D-Nip? zzp&?SSZ{IDqNn4M?j#o=Kc7x}{uPw_B6jD zIlmeZT=?xB&A5VbkD@PFTiFfscTfjr=o_s5Z;w|FU(LNZO@qu8c!GX=wdV7!rnd;h zxn3%e%|Ckp6~mWWBf+RCrlq0?;-GnRVtC6ZbB+XD*!tbbXJIWZ=v$oToHp1WTFX{a z-O=n1o208az-A6Uz*V^VUcis)2mtBDS0fE6V>vk(8sI%L3<4|>3?lFj7Wn0dCH{~1 z;;_^(@Q>fa!N3HX!5~1}$OEt7f04j1_#VhB{I>uYB;eC?;MX}F?q6%f&~*5J@8LRu zYcN8}B2rSotFoc3krBww)Y_iNi#QMX;^}9}4|XsxcyGYJuu@8|4}tzC%~Uk(HRNQu z4Xv#hK7O(`Fk*1F`V8&|hS!-Jcxz>3|B>9;$`WM9?aW8<*n%5)5B{2wg8Z?Gy#*hI zhMXd~h_$T|IXeRr0}};53OPABuk9yeZY5E1=+Z1$f|0@*=_1q_f8e1(yjfr;@y`UZCo{+3(O%-P8DgQ%GmFlWFs_&J!F zcpuyUFIWEaiT~wJjsLupnf2ZO?biQt>Hm4Fs-2Ooh_w~)Ond(S)ED%*|MlX(nP3DD z{l82FWS$?t1tyvwg_rR^l*W(phptBp&?AYNsDcXc3RoHVAFLtpA2s9^cn?czZ755} z2?HYtBPA-N;tac+f>eVec77f~T?0o;Ln!(l3p0vDR1xd-28r0~JY?h`gl@vXWBPT=Ca-_3U8 z?KQ^|3@qmV@Dt<)cOl3We&Yrf2;sl|5ybS?4<&~E?;8O3{-49)YqSIKDPh!glHr9>O2*m{e~0&F;we0bX4xx zk=E`XIXudRQK5F{J>SmvPpW(DNJo~((Jgz8Vqrt?$%ykVX3NTmgE2>kigl0eJ%5^p zYv2nRzBfaCo>c%dg5g{=bbx=8ycyv4>tDA+ZQEy)3Z|a$&Lap9qK+>Sz&%e}INd!0 zzCo?p+n(@qIs#-h_&AdT%`qB7*&$EZq)6`t2{TQMzxsm&73!8ez1REZJx&Hxzk*|c zOrh5dqVSOZS|tVJ=D+BZbjpjohYAJU;P)D84DIl+Q~l1HMip3({R!g3>NsNimKBo< z@&6kF60FXXxrhu*#DCL}3bSiG=Jb*XGFzD9aCN(s2rEpEL|@^ci&*Cg$!n-m$VriZ zgE~bRGs7VL)gf>GO2Q0vI72>$g|-$z-hB7h6UUz#`b5k#1bvRorf0)2kE-!udOl>t z@sc30Y*Y~kDmHKf`BSJ8js#&GSx8+8tss*&B=$Jq!UBm%3~1|1A;CDw6LKH*`p(z0wP%QvAAW1_7Vm9L{UCqT5}JCSs_c6JQTrxS`NAM<1Q->B2?@;R8C2#-3anx0y+!S6(@#F znmyLzfOodBR^g$oJu!!je&11}ym!YWV6VWRw;SZEu-`K0KJ8#8EBnsCskxnC|3&c~ zvbP`|SpRDaVzUt0?6HBqUa2wZHrk?R$;K#suK&D5QE56o9J|ySL8V-0Z?-kCH>*-> zjy=nH_|NPlUjjBVTbbiDD6djH*K0hK!s9g@Q!u%Cx@Bj7ds$s!dPKuQx9}o?#UveV z2k$AW-8w_|uwnKWRH=oATG55Bh&1Gy(9aVre|uiSf~Vw*k0AA)@r`3quI_w3sOa+M zpm}S(U+XljchYpS*n`!gs#mQ>tQGO!^icz5$YOxF(iT{y`0G=*?Csu+Q;tqOM*QSR znb(19-NU_Ca4(#HkEC_-KRcs%3K)fBSQ5Jt0xrGg9|pwZr4+b5}V~W z+wl8W1ZmBmeex%r=l%E)3^9PX>Cv8p^ue~EgRR)B`d~ri-m6mc%B#9zq z!H*Gy_fedYQ)Rk8p5iziRfwY&`-;z3=h&goaGYb!Ckv$&_^$}~zMX$GOj+TvzwYA_vFDe! z+n~4U4P=~Yx_gr4vHEhR&I*4#PbVg|JH<~v_11@$Wz!AZn{_2tZ z4Jql?^sA!x@+;yF)Lu;{p&X+hHm}dpI=nq*DqQT(W(KRw{zBXWrT_xqM|li_pLUXX z9ku2~)2gFKjm`d^%zM#$A}jA&F2}-5yVHX7VAa=ool>mZD4$flo1WD>F{3(p+WUVTWO!dlLZR8|fNo%BaoQq`!Pi=A=` zw`!hg6rBBR=|xvFzfj{1s-28lslKl3RTlo-?d9^QfX&?{haxIfeIczL$E#-C z#(nzA81<*}+iy?klsL)8bKfV}Zn*Yfa@~tHV6^{WY|0>H!T&=I+NfSMm){CCi4zHo z7!E+w+B!2v+gyv2QCycL6q|85i3_kiuhbIz3=8%#y2xKfK$MgOb`Dd;M+dLT>YWa% zU%+!$CRr-zdTzP6o(>n{4%1V*DDL3YI-B%h+%bUPn*JNqZwUv|MX{ramjd2 z7g?|L!-3aB=1Y;T{OS1pL^hhI#66^=Xj(Q~Be*Q6=n$8_`3l&`#2fV8HwIo9rrSkJ z^XjRbD$TpnZ;ox18%MZchnftMNFeT`PxSry>gPXa+&^27c8tFNFt+zyhw!d5j5xr{ z_GItfjb4+xKaGkxb!L!Vmb8(XU~@4=v6i<@%id-BB<4(mOCTHaBxGB$5e0mDwaB^* z6NBn6f;XW~!MV57MGoZ}IgXZpz4uj$%w6hfo;Ld-VJPo#)K3LegT_@*JI6hqjRnnC z9c0L-+~n%EdTjottUGAB**uIg?EHKFPBl+^;MG7xz@~QPwMF(rx({DahxSg?RJN0? z0-`PvtzEV(DSPUkTcDF0iqMDk0+f@H!P#2%gCD2eb^V|g`R#7P)7$jSGMDdnRHN!8 zIyvjs_MhGMmspRdH|nvewy;Fz1|nbbxz)c|!8UfrO`fM%eq8awf`FqMjnlvVdSoZK>ahs@Wogy8+TLh8G~%e2u~Gzcq1m5HO* z3fe4&&g_Fb))gGHJ913ARiC)m{v7>e?%h=2cG{QV_FAHf3NR5g8c2R$^Kg68tkNKA z#KOpBY7k+!!JwEeyVZ|&HWh(1iNSZ8&OFPBtEZGjgVxVcirW*Hz0`J3ICM;xK{lY9 zZaMuc9s{0NtIoK{*na<3EQ-_%vYjmh0TVn(Vjxbx&n(=%Ox3;eE0Tn59zIwdC$=) z_NihbtNm_)b?)@e-Y1+BI>ePE4r`_Du{{3=>rVZPnKQj06V4okIJGG&Rh$x?0+jKB z4fB)c{pPe(=VhCy0kReiJomG0w(FVr#@jDP9m6XTCfEye`@&+nG4I<$E0#NSk}$HR z6GoE9`^^|{H(g1iq6BW4S_~b_zq|fAyqDLCi)HQ$ zT5*l>_vahsWxpEms5J}GRtJADe$5G)@{F%QbVP{jYcsrzYdTPP6fN*jp7GuJuxpu~ zYyrWLeV=#pLyc)xP|iIeZ*CwLwiH{U?T~$^vzzv;vs)kho3|- zB9N&!!{thuDZ>bRvEI8l|8W7t!iPs!{;6A1tIBg<&FKE8<4I?h!ROmaJ+I*#otx3B zz54m(Qsl(`OOA_Q`c_trb(3e*whbmeU(LT%Q? z-PBs9%v_SMQgtjkeo|H4RqiwinS=$)oovXbH@Ku>85OK(F8K&RR`7dhOu!k)EMqo3 zl917MzRx3RUA;#+XZyDo9}y)6%k%y=D&t^ckU!_5?i5#QHQj6_+iI++erNQR*^txj zl*obOwgbn4_>mM%3DTc5Gj?;0kU9q|#@1Lm9jki&u>gl>9eu{6e{kuvC(>vr&>!PB zK~x}M|GhVK`HH}Lw$7K7J(l#Z$V|Wf?$jLD)93@P$-XRewnUGSV+QreH$(0ru7Yb#42S!w!e?pstAjqK^kL z5bNETT^(1lem*oM;{Hjcn!own$K}aUN%KOw+qXB^HV1fA{Cl!V=U7XVt??=LQ}J0} z;2#4$@_TS9<+yr_6S7WF{i0bRlV4wj=aeoZ&Tsn2MgyoL{}Q&}5aRkvWz3mln~mzW=6A z__+-N4S}1MlC)092x%JL#1wSZZrm&BjgvJ>$>HDKm1lqLJo) zs`lHI_K#-CX6@+Jp#QC`%7bC4-ywIglr4qVrIQE^8tc551!n^^E%7%N{o7op8da9- z-d#!hbV%*V>p<)T&HzSZdMDet19#QCoZWhn4vH5tj3ky~{U~+g;wNpEMNm3>V@rMr z`RVxSCRF2uu91hXlxQ}lDg=`PfqNE9XEr}{clfs<1G^wYSvpE(%MA0v-?L1N8>NAq zG~3zIz@0zk<>KLqulc=Hcj)%2=fy3}d96Wj3A|i(iCr6OH?o}O4GAwB{v&8@Ug#aR|nPcg1{Po34RI)0=ZX;~h1IG$T-Wk70x7osM3*Ekcf? z-Tcf|G@AvU9i0W9eS=-aFZZup)&WKqw4ije=SCJ9A zPCqj`oT>MW%Ba+t&8F}6( zzcLw24O1(Me>KvdTp8IJmbM-xVL*C6RxY)|ed>unx%>CaQx>!9&@0jpJtiazySbLN zUK0kf_wgTheNah(eH2R=5^47}7u^!~b`7Ms<%x8qw(gCA&`E|;@>Lxg1uF+k*F$ek zJtNE~ZHszKz*dAE!*;*gpA@p*pFqvtxKg<*aPWV4KZ zbY@CS#VUDpXfJl(O_&Qvw>bB#M&vq|wNm`y7Bo_*Ekvvz_sJ$Wd3~i%Oq6X2B;bG6IZg`;)sXzZy zU;Kh|aHEFy$xK{KKI^+SmB8D4v#xNyQxMf;A2S;-bG3l)5s=?X$>1yCPfv|!il2QQ zYfh6vRZMf^^l?A;(NET3Bu51n(pzY>T04()QO_>4!RF<1GP*PA^)kotJIALq)*}(0 zlhy7~l@cKfj*5B#J&`=6EIiLgpO;YE7dLx2vtzY&d4&Cwq4o%fT-$Fh|3YCGPXLuO zRzc~U^OzN2VefUJ4%1SQZmIF8j~)9ZhKdcoN(q@3={ypdUq z)ZxcKih11jg;%p-IEaWNU!?iLrq^&3fe^_AlB`ZqfzhxiB}Bsox%&u#dzo6hjdYW= z{h;!=KMQ;yw02dL-llDxn+V86eOLWW-gzf&;@tF5R&-Tg_)*sjB1rQdg9rgew3q=tzJ_w1=(W`tQR z2v+>p=QojRC0ApG&D)WK__tCg=`ERH7|Hx|2FMG+a6l0BvweTCYA<8p;OryS(GzKG zU>)fhzQLcIaELXUCntEu3ywH3eZcGf4lDHLd`q~}Tn+CNfbPh>;JLs90p75!&Oe&V zPCTm12R_hjY_q%bc}RDj-eU23VmC=HN~il?MT3hS_GWE^3+J;}gNUsB9|Jz0keR_x z@sC>%^%%bJWT7B|#+@SI-AA*f=A&PtL3Z?vp%Rm^Ky5-eYO}|Uf^^ifr_I$y+5_7p z-^3dNlRuFIm#g-8S+WVzcxIqdDkL+K&qvD&Dp{J!Ea=b zqiK)3PJVA{JH~GFsH;4oqb$tG&25nHBHnYN-}e*DV0 zG*|geEck>rOJ-#AJ-q+y7G(v95R&N&l6M24)4l$l@fcv+@W-P>js=}bC17YxJc2|L zL*N!uFA<6OV`^T7`!pRGn`rN3Y7jytvmIh}T6lhx9e4Nhu;J5|Ing_NI=5jUaqwqq zn9#V%b0V93;!g4Vk_EJj=~|{@Q|cn~%j)JcCS(mAXUcfW!MYD69jKigdJ$yYBB#2_dSE$T|)aWWm>Mq_mj zvY4i{;MB{JyD4RccjR1qXN;p2p;ZkU*<>Cyoj$IdAO|?zg}-t$HW89vk*rVKi0?1$ zlDX^vn-ApdPBtI56r5sM>@gdGq1?kqWir*&(Cw!C3mj#ge5lHdFYWM*510MBlBS-PO| z!dG&#OB{9x8lW;vSqp&TXY_xW)k*}$;G zD=NZXnJ}ES&=(vr#{Igl=eEhbdRN}56(!SlX7Re@Po2b!S;}5S3Xv(U^^|L?{dOJcd(`3=n0J<>BkJJHXR5nz*gq2{rNcLS{Nc-n!@JQO1sNoa7g!dm zGvZ&Uz+00jtdB-z(sRB+k|x`=#F|0?j6@@v5~*1P&Bg$U_KMnBiN${HZI#}k|V5e?m4p(16z{; za(m*uN?eNmpN5xKVA5fsA=RdTra!EIgfYSMAK>lX4C(Fuxdbni9~u$_Mmed7`)LWu zM+AvISkCr>?9z9^eaAyUi5$WRdCS9<8FK(To7gLXZHa_;MxSS??@F5QGY*;s!~g(! zJQ%~p=aEDNuQz~!vq_%kG-ZH92qWNh`g;{FH3ZEEXbKVX*gfNG`v}EYI>N24kcS_@ z4*C>BF&Yjs;8BQ(%pOVUHjn{FiGvkU;rQbptA~jKa9Tgozw$yj4TBg61EzL>S@8^_ zhetjIYnoF9X-Wc-^CuXoc~zv7`;T|~;tMc^)VUN2 zkbP9+%_DmmKF))%2Vn|>Bmu6(S(F_;<{xv|4^V$>@M4Y-v6Ze2y(iqbW zYz&eX_}l-Ou^(WH6ea%;gry}{16v_xsNos9P(1{{qWA_thNMwpB3Y2nf`TltQ*!-+ zQ7<53%Yp;2mjqp>R=%B(v567^K96@~#{c>s%Rvp0m;t^t#}I-nNEJePYBY)rF+j!^ z^ct`ziBBtvZ~qyEER3Tvi{dA!*#DoYL^(;{eRJqNKn%t)qrC`&hj6Su`cHA*CZ~PH zc`I0&)bdT#6(aTjLNAl=t*^ed3wq$6Jg2tVgT30gR(zP3wL|A>L~d?YV8nW%I$fVGDN z;m|O^jO*~A>6=Ftd6NS3on7_jEjhu?md3xHX2F&)mHlM`qBL*s5SU%ceFW(IAEp5o zohQfCnNxZ1L<(i-+Zkc6f-!fEZx`TAxgcFX00wtw;vWwkBKe+iDYjeX)6UK3(2oB1 zRVh9{-(WKPG8;?3*$eX?fh?j7KX3PyXZ3_H?))_&%&*gJHxX^C#;pV$h@d?dK;ahO3tK_@{T2%zij@=!a;KD=NV+{lcbj11 zUqZs-y$n=P87N^0k0OTaheqz`U?*T-fmivDe*O!rwkKT}c@VLeLm*-` z?GsnvLB}EEz@K<#KAk4_@xiHKTzZ*x+0)Ui2hMz;U^rrE+(LjyIz$v~IhYZTKG+1m z*O6spI@9y*Lt>bzP>86Cq}Olt=#qAe(N2ZodJvwGCI=8|W!~ZmH~`8KF^=K90L0^!-5f-X_Vd(VWcH?(ZRg0mP#>_u@k$ zs}Z}^VpCTl0u*3#0vq5xOc-1JmO>I1q!x#~3i(S92%T&q31BldeiJd(DQ+i#7Gq|8?5*^X0I+5BU-`G6h1KlgcPwUv$#kOD+b%e>(B6y$N*XDP`t9QpE5 zB}0_iC1LXlAjk78YwJKZD0qD`%9I|WZ^5Exd0fn!gl#+Oq$?6uTey_jAF;Y# zZ{@6JbzSAZbaul&=A&qPbREl)zl;ibzDlujgc61==$#{I92 zIj9s;Tl@TXy3SNE%%&bLnjfZ#JP9whV+z1Py^9o2RnbpYvyq@3JMmfEyY^u#m!N!|b@Frzud)x)WLNMH22@xKyLc)YK3+~Y3J%=y==Ep`=PQ}bVbdMB zv>c)FMkw&Op@Vugg5wqq`@{TP#j7y&%;^$v&X8SN6db&$$T;EgEdiDA)TcJNW4(WN>kb6}De}CWhJXWf zjOTH(cVSFuD311XBunP2MLoyDNNM@!8{xcsU{O^63fsQU-mLAO>O&h&$XGlAOA%iir%H%yoNU3u6 z0Gmu)03T*c{G@ELa@U*E=HXxPFSu;y`Uc|+PdeWi^c#dv<~vA$>$mizFd_AX`T%YM zTird=PYWnq3*P3DI3dTxM-aB+aI-(q52xn<&4|y}Vc=*3q^Io8#6XVQer;EL?yAQ( z^pJrt;_)B1SE)Y=Ka>-EH}}ND_Q{q@irZZO+o19{$tsc1>>c6vANO+ApgVc>65X7B zEytk|BMXo7qGn&_!O(TT*`vFQoLtW~c92W2%^SCwr=4Ob8<|9GG}vBQH^1Q@D0DHd zfT~hLmS6ZaYktolUi}Xcg@DAv^xaoOidz48*jy5z5%Y(~GB|oLRG6Q+Zci>ga5TbS zJ7jHle#3SD%)i}fTSCC%DnZQRb`{U!#2iM-B32`E)&)h9izHVQhGzVt6{3gR>BzB}=S##CF6 zT*6BN4ok)Fbxu3vxfn0lV*wb^iin+EWvyrOq+a42OfeHkN&!4)rRDIKnQDu-$L2%1 zY>oPb-C96FUi96O<#O0TtsTDUZjIUL_|d^F17lfG6M~^UEz<488pqF%8*+Q*6QKP{ z0?(-^q~;dS8+vtZJWpaaGZ{M38*W|t8;Y>Y+~%Nlt9esB7jlxvxal_-XHy6@DV*tV zp9y7xXn5*1CVKscMokx@q2_uRX8^h+VgaG_r*i3PpQN&@=Chr77cg9O`2dS08z|i% z*UdginBx+2$4j@EFc;q#&|@7f_#W*nka*ImUb5)v^lb#dhtgbW>Zi!ohkj&^<;$6{ zgr&T~Y^f11T3<&v@Gp_$hT-V<53H2p|fKuelrm23rPx00Z0q zYlAu7n>#rS#&D}yr}sGn#SS4{U*{@>#5a)A`P?vu>&}w+Cd$9rtO94^dgDqQWvS8)&JOPr zv=Rc8U1s;k-5Kuv(eq;FLLvpwDg%{te*#Dv`j0Yg^~>#X3zcGF4_LJnz7C6< z>+<2541eX&%D7a#Ej^2g3B&9|C{AeHRc13+zTvHr^S1gZwnosW!I)rBkl$vx!ey_0 zDMG)&Y6r#jaw$;sJRepa&CS3_3(?TiZ6U`AzihOG?Zu#h?oGEb5w~Ts)==q>##{sc zcdgd$t*C|SYvM?c1xlC%;2gb7$#5J*Ibr+-3QAD=e4dh{jO*3NbUG5gkP-r_*m9g^ zE)HR4b3f8=PmHt4ICYPVvwqsth~Olip#RyIz=9%FtH1Qi_!6j1cpX=ltCpF^JaOBN z`m7x*S3kKezOlh_OZI*Hf(O#pUe){;trzM&N5;@X1d2IORe2)5m%mo##Ey^33mb^f z5eAoBhdQ@tc<|Jpf0rApkFb!_Gj=;YG=V#sF5ke`i4{8c(_UDb)C*HE=^6XBuZ?Q6X)=E1&UMmGf|;?A*|3R#lEkzj|keORWTQgW7NY1nN1zc z)ynDAqn&Er=8XzC^L{z(^t$y{UcM4GC}OZM*m3R7lt4>Ym#&}SCuMVe-k(xE_8%dv zCm2;~#07%*QLUsUNFs$<58i83ZC5(cf5hp!>pFKQ)SB1WW4svoPNQlc{F~SBa#GKF zh_Cxwu)d)+m(8K&T7{X;N(~+7i;6nkQQq-cf?S8H~XM; zCZBy1Q+{hQKmwwuB2l-k!3lk1cfK=h$7D5C8j}L*8rUlc?Y|~vjm@i(%F_L%w=qQz z%K!-*TZIMh1CVaSEZU!S<`09$>oUvqADFQZp~N8Q$slS4=h(b8zI#J5KM`}wb3e&> zobbu?{K@o6iDdX2cEzta7gTl1#S#>ZqyWdiog=uNWM(!6KYcn-Of+3?l1UTO`~K=U zt6!m%bD>9dC+`7f(%3hJyGg2q!-D)9qjrUFfLFYJ9PTYdpvRhfH(Hw5z3giCHZn-g z>G<@^3@CrqnJXv)Ulva{FoGl#h~o2GvbFAP$4_P3nLiInYBm|91Vt^xqgH&pqljSS zcj41tAh!l83xZcq6(X@)fOTThUzt#CIW449cg}I~WH~uYztHpJwv`n60!|8tL^VUo zr2^U#1TB+90WwfyU{Y+mGy~l4t_MMdE_nf;oEBGIXKU3c6({4B{M88uHuiO9-?s%+ z`cM8^voIafS1_bUd&Q`}L{G*x*ggl$hwWaT!2PA4babJCagGcVI}0m`gSq;Tg~kPJ z-3E|)H@=Sb*8=qdeM`8d>rZAoCTXK?I@&ve1!hYS6yOkCleb?~p;48zFoWs^-8SA} zn%C!jQ;y3j)%q&b8Dl8iq+99QczW)-&d55L%w-aA(&-;Wy$6a0rRF7doQb?*iFXJD z9GqSlCddaYL0IWXdvJ0Tgcq9Tc`c<`=Uk^3?ZPp;PR~8nN&h~BS}u`E`-@8wSDz1W zNUX`|F|*-%7b|~D`EE?F$uPD8FT*l|1pz?P14YZ6MH=if(H&{DF(sRcYrUi+rARz& z8lQ@9qptFOo%gl5Oovq+b^3c>zdF>LO&VW9y%IaPmYbH#3vp#j zS>k;Z1)}J&e%BYq2E}Nii4v(d(m2_x-O53{57#@4G(ZJFr7LJs9&ttKP00IJ=gD^w z1(TLc=lL%F`^^t}AgitUa_sRuWx2~r^U(wi-fA$*|KoIP6pJxRqrzFtf(cr0@GS`J zwugOf7Ws8@)#N6_OMtwDO{c-R8{VnbmXSDs%_Sqid|(%$Cyg;eayI9HNweJXy9EF4 z``o{FtabqwZE)5sW^0u8!9`|9Nwr})j6fO2myex(BFs^?jb+8-NR8S&d%ps4937r@ z7O3@Q?@p7QXsnAcbhH>1)NdcNLAVashsUyqH=T9!6&5Ve1!Rx}t}u^DZuUJw;#=d#DAP3lvvXIh$tk3E=guSalxxh$ z=q>@^`)889q_^4g`)CzbpPU~#5E8Ka-5cuo-%pE8RO^-LHS6TD|2^n8h`x89`{OMp zjp|6{)&em-&k#`1xtH;@Ss!6VI;Ll2sD+R3IMxt!76KIXGZ6PGvD$`cWx9%OFP0P> z%2*beREyR?pf+S6M0RA!KwQZT6u@RG(XE#U;>~iz*_!uO+t;2vSm)DKI%8Lj{GcNs z=3l^`^tVtiYdMi2u}nv<=A#jHsZDAM#_KRH#o! z{HJiTXi=8W4eji!4tiu!xdE4twy^K~JyAJ$gXcE9&~z!K?(Nn6BQj~ik*`m8qBhvD zvRkor@|YG^f0k*PdiA?ggvj$PLBadWk6%of3j(W+7{CC}cPdZQVw(=L!mSeB#wRTr z{!bqhBMoH9o9va2%*4vRxI2S!Q2w4jz;}Vmn)tz2Wug@ri4+}oAj5YHk|lZ%SjHqg5~aR2ZBN)b z@QchBZ0G)XUY+oXlpBY0aICG^ka=C@hlgB1*mCu9Z|HLY{PtH87A*lIv|^H}+Vg~u z+xaj^ebu(bBV2JngK{~)OdElWE44?yH+BGLzuow42y(1|`IY$)l*LqG?Ap)!E}>Xv z;6FD~u5oiR*gP~1Bt(BAZNRW)jel=QWBueXd}orG7rNP{i&R)@r>hoF8BR+oy*on6 z6nnZk@5Bj4I~g=Ck7kV*E(d8?7n|;Kn3G(J$_frdOU$qyPq+c(1dx{7s_dy1&d6CL z*+9Gs<_&_d5y#%2ycG$rE)Q_PgfD}8K|pMC-(Yifi`W}l!V{O+0p-N?d14J)T%dG# z?K76BuvZ!_@#WFI%)e$qYAn{R{Dw28C$uWzT}WLi;H;Q%XNF*YEu3=B&BS12_&*$eUw?EwQrf+Sc$c=ZW9>gY7uoC5Q1 z*6+$h+W>qDdoQc-1B>#4iCyr~RFPJ7=+XLHZuiv!(a~HVAm?NQmF`!Vk!3$0#0PIg zBP}8ln?Q+9i*fKNME;i$a2@?U(o3z!=l~Y86gU9lJ-db`0R0@q2Eg&>d_?jsQ%z3} zAqaesCs0|A{Or~!xwYp*09-H>?Ya-A0{R!#*J$Nh;-+(`&1^G;li31RP zZtj?&d$qsu!IlmjSA9HY8N?IJ0T<8W=9@_ZEq!r}#tVi7b-?LPfFWo^P_%=JbeIOS zo6Sdy+fR9_=YrclknYv)g;Pkm*Ka^zn?FAlH zAvwXUoev{;-rIQ~tZBodl&Qd@A_O(S`{TzVR+N8K-D?V%dNYisX^XQw&^(R(+OdQA zcy_tR>q`h<)cyj}#oRo%DHEg%rALDJ7aBEWc>x5m7;q#~`{#RTJvJRUHwGBa)_HGs zC;*5C&YsMPLcc;uC_zks2lMj|gifiA1Z1dA)#yafs45x^`v0_q?|}q;Fg~9FmJ0PB z9Z!z=3_?1Wedb6b8?ab! zXT5*@V`;PjvD(Cl)*xcH!$HPQH-}~l59wqKd@wRIyCs+U@iPoEf)FrvWM?2a20sTL z1L+h7P6#-#i3&&h?f)+-aj>O(0X}*U{#yY5FY5U(1a#SNJS!-D>Jy1Y4LQH=t_+kd=+4almFu+`ji>s|9lKxP!Pi>2rHCXSy~~G0{>B+K2wzB zisDz* zKt%*36$AkV1nD?fAT15jA>G}Gl(b6cQIVGJ?n8HX9J;&V+{s`ka zPwch!+H0>h=iHOrzt69JvyJp3ffJI>1($-%@gN~-dPabf6Cv7CTn_?`io9Eh* zkTaqo{>o==pj=$x3QoJoUi|ZB17v*YCVYi)^$;q+sRm5vpP?V#UA_C)>*i<_)4O-q zZ|i}42L9h}q3dV-Pn!DWy#D`BnEn5w^#AfD8-nI6Z&C)(k!U(&syA$~h+Hg4xcI{exRZsh z<)y;?relZQAS>lRo4I?8roDU-=rWqBMMLoZR(bRjYuc0LUU!tbUQl&Qmo4{qI4~K@ zauzg02tPPw}fAvc0tT#OKR)BiyY71Y;!v19;Z3?^+K-Ty_?LKiQb=)o+>rQbj9Lyl`Pebj@aTQNMv?T zmfESo;MzO0nH}x<9px~N_4=LORnUjM>_4|#l-sB-m~)zSyECaJ*{&9MudqX7wlneE zPeb~+4K+$OrPL0ULC4duyT)v9E;K|o22<^@X!>LfH-^)lq$8@d#{b?B)g3z!M@k@s z_;dE0xo4m`33@?g7l`(#;}=0vQ3*;mY|p;gTygVNs|&BW`BBFX)&P<&hDHu?t5dn3 zeIePp8>5Vd)_mIfF`PNX^-VUXx&A)h%BY_jc6!ZIiNI4fQC_@(U@nxML({ABwXL`~ zu@E|rfG|Ga)@S7g0P$Q}8`&+e?8A!@px%x&r^MTQGv5}T3_+YR6EaJWwAZiXkYXyq zw+d~~G=tz7d}>`o7s0$=o=XuCG193bmjJ;EWD)?lJd;JX==^6}Z}c?mmyT|(kC%2; zL>o9(bQ#^jktJk=6U1#0ww>V8yAv(wIUDN!;MaNK?1)6db;U6v**? zo0@3Y!FhWmzl%+y_7jDvn)qX`NyJmL*8b9$hC3#O2G9+`w%us0d&14(tMu?BrziOj za7p{`F?+pvM9iDZsMk2#;Bm?($@8$|Hjc-Ku@1hjxj&`W_m-+oH^d6~t6(%$XsXer z5crK?V{%CI-p*PEA9>f1N8puRTCPy8 zw=n1UqzsIXYY16$Wx3~F9!?sBN+ zhb)Be5tdaVPl$JGPEw#u(g67uc+vj0)N0`ls#zAnp$GsTskV5YJOieD_i(sG=LBhv|8g}p+7_bj znc@uWI-qTBbZCVllr7JDzG4?l!{>F` z&&Y-=-Z_VijIe6p6+1`W0&^zh&%HoiyN!2aLHiW*-vuV93-tE+cKco?)WXQR)`s zu2qa=veeq&H{ZmbAq?KhIlfu~ER&z?#+)*qC+zR0hK#%YB@_7Ic9b-CCi% zKfZHQQG!(TRdAa$<<}~n3*5@Vc2|jd5Mz3pcIo^W)UK;I_X8ctl>{8vEiKT2{V|FQRO&z>QZ$I)mZba`OM_EWeehQ}o~ z_S8rfj3_JQqHcOVQuITSiSqK9H)YVLFgt&V4VO%fheQE=obp0|6+f z6)XYi@~zUfD2|QuZ(#T2k>HMTWTROJM)Y{)MW2%M#cZQkdgL~tl;(Vz-*IuWP*5xE zholq<^IgQdiYx>W!`La~;28_DfjvC*#iLBbB`xY*?RePT#vJ*xJ(|_OV0VFepC_OU zG+xr|I?`cIYau<Dwx1{7Qdkm~$QK8`&UnDajHmGFot)dy~8~ZOqlQ!5`3F981V>x zs^Nn%sssW+d)E+`AZ*w6Kwh1tYe*7CUK`eAI?&ii#OZap2LHXJpb4=E>@+GoQgG9{ zUEzaEDJ(`*&M~fB=)}Lj3aQJicEzr)2z3AriF$r!2oh78s3UY@prgk_nbH-P#AB<1#fkX7KKPut*rcan_-|IyQ+*`gEGu`RDD(n;+XWlu zS12Vny#ckrf&sq+#6fAldyQ|Or$|7h2xvRyGH^Mu-jZDcLh4b@C+5!_Q2X?)7WbNC zsw3#Nv2z694%|gNReZonkiK_spSDi`&?2d}xa!Y)#T0<-bcvq%CuDM*liG zhwOwst&0x(lFM8!lP075usb_rJ!aPgQ(%ex?b_kbB9U8nD_$xrh)47FT9x5#9^C(u z7ZW@oc_Ou733Nub=}%^;n*ds+O)e>K@%#iHLX>8-KQvBD)4Vs?S+*gSa!4}NHij7E zcgE|+fx((xhR@xJZUUy)rLaJnWbgYZmcB)poge#HK9a|E5Bk_)cut4& zmbh!d@jU22&&hClq@IBobJRUfmU|YN+D~CifsaftuR8(Mh zoS#-ov8R`FFn=sCK*wc^khbV`S50NE<;p!s3&#VIc*(WMsL@1ceX;4Se4FB5DbRP8 z{g3VWa(%duC*ogPmK@7`bMG4#`UBb}QPRhOrEl>f4GF)+CBrL>hSFc}uaxT9ov&V; zF8k3@=$r4*67RMTAZ0s7k+L0UAHQ;91LRFpHU;Iw>exZ$R*#6LNFpsl_&nys#P!%w52+?3J3leSuZoOO@scd`P!+hb-;OcI$k8d#yv^Lx~_M z?Fq~wRd;!p44F*qO!mP|2|#h#_QWvvO3AWrXUli|*5EtWVrJ})2HeM&jAd-T$&DwZ z0c~I$*84=&y;NBnXlV3CWI|&bGuAoyP00p326cJNzvS}VSQ~Wxd~|zI1hKt1Tw5Sz zDp~@f&u0EQOy~*z#6)U?(`MSfFDcq%Z%}bbP;!nt+Ze5UKhNqQ+u}#y6Z<i5ir!g-YmUnlgaen$y zqTzC0A#1Y)%jEH~fX=}xrXI0*5uLut;S#sfGv!C9$|j$B%x8Xw_qOrjQ}oxz&7Ay` zWQiL1E{6;rrN)a7A62k+9gsVfm=d4<$bDH?^5V@~S;=@FoxcN9ttJuo9}mO7ZnMI1 z;f=+7C)f1O$cpF}=0?x(Q-HRRSZ!4Hv2v<$@l6;;dikfEC>o^n$xFvpvc3@VnKH=0 zqPO4%+c6>{(g{v9iA0y|FV26^>mXj-N0h5j11zG}V`M}B0WXV;Ic`5qj4P*%T`C|X5 zdY}Bx9vG3Wz$VFl_YB|NYsyqbvAZ5d_uJ+D_ZCAQ*7N?8eLZXXg$DUEMPWA=$J@ zRp=|;Wys_+DZ)ThQ)p_#1Z3uZAZlsX+?y&FVue*z&}4cv*=j zFn$A4olRRav21qROoVA+nrg5SsT`mw@|2gIGETP^%-or4ji7%=EqwdVyHQi^j0**@ zm#&Sf-7ou_0NQ%Y$)BjQliGiiQF<^XSp(eAEf)c>`)-cBJsv2c)!^V5P7v}*>PIYaU$94leTIp<^u~?GFQpORDPNnkclH&zI>{3 zF?FSBA`I$2-h0He;Uc-8P?IurOei^x7h>avZ{ z0L+O>Pk~E9zw#!vJWCNlOQtQdLknjcQ>`15WLfz9Bp_L)Gmg8#gpoT+7PrEsd%8sO zB&6>NNYp;5;M1?{(6S7^DT;+Gu8o@hWFM{&usy*nCYfN3C#i*P1C1c*RZia6SaYsr z2*9?;21dPT{9UMa+J6LWT&n86neR^F!w*@HsJ?5&%b;i|4pBP^SwZK-@&a5r0C(w? zbh97m>KmA{Q1)TiOGEk5XTz$oPv-X}ca1SC#g;bb?GqLbeGBDD7kEBG%r=4(?m2s0oN&3_6dE+w|1Y z!+gA?%K2l_`rLoZz$od7cg4r=J;``)4qddsfq9JU1Cn^O@%^!L_iSU68-s1bSb_Lp zu6GGhqhNV`{d`v4&UT!ce{4$_{pbzP_Unv>UGKnbQ5i>F#>j+itPgxHVAiLTVWR!p z((KsF0+d(h)S@vR94|wY?7btIA`ww1&ZUe~0~-=}FpCbi)bZ^WV-{dODU)9fhQW-R z+TErq4AYc&J9%&#DQ6VV-%9%@NcKe&maTr3t97_mFZmq-WCvbym|JQnMxsWLLWczQ zb?xr@au%s*n=qwzvMk)VP_;a0-+tT~@tijmNVX??5Y50wn|46D5XX^*rZd?MbWad$ z7>kDE98YH@?9zvh^cSK`pe{MvMFn1g`uuG+3hwIpJH@1&%hMjz+I^Jl<6xnHd98@& z2#%#hP@2^aPo0_^E*b}pjHFGxMNu}VZ*Ddgc*CM#SQCFAw1+IrZS8l;8Qo7a59A`G zf4`NE(S0UX>&rmJEkU;{-}U_|aYpRLOOt+>j)ZO&BzwKf_@0WsgJ^9OXQlD*l0;!z z-E(&+Aff<#H)6N$`w#;Kuil*`DQG8k(25o(Tp1T1WIJEgzW>hp2vA$7m_aW!^h9Us z%jCSp%@21TM_q&-i`EWP=Dts!u`WS}gJ517=3k81d>8QGkD;t;n^P)cYTy7sVrFPS z95*x-J6IcO*n!NqMLuB8)jhgGy&ZN8ejjswHcxphvoF^Ny z+19{#ZB+j9!5DK#M}3R^=_gN=+UF8A5FtqNcino(79(ydk4}Rjk96t|oeVl06r!F^ zS!YFyWV0KKX^iiooGAc3DF0B<4La@_xqye;{ zTx%c}>58xTifrXXEkhxjbA#>B_wC4rAmvfqAM0NCaV&WjyaHFITUpgJ4+2Z&+&9?h z2Q6IoC*j79skf z7LXi&`(pW1%-Kr?8HrVj?F2q`#5BU6fHkj7*Z%~e<^#WtGD8dO7OmvG(x3IZ(z%lE zcdFRraMCtN$zDprZn9=N9mpq~P1vvoTLUvC^O;35F)>j8cdUI*3_0HmE$EM$S?RI_ zFm9v*#n%^jm+;cH8fs?|2_IE_()r+JFGOn(d!7=uw${U|GWV<2B2{ zqJS)CO&BR!D^FW~>y0nDf4*4DTxz z{&a(M^2JbJ?1UWWX?}T^F6nybj$^|Z5u`6A_$9}^XHS2$R(LK$lrcvhXjFW1aa4e^ zuMeU}Zdb&gpChKhGPs_sn>3gx)5!YL<~{RF(uKPsyykM!hytn?@0@jcvGOr1KC-Gt zw~J#_=l3rW1}X$lWatalY=v_y&ptk$zU-TC19h8;Km#BKw0{Ds033FIE9ZKq_`c5L z*7bYDFwfXuleNANo$RZ$OCjQu^A#9?86uv?+}W(^Yy$c#ffDOwqx=pknH7NhGZo&L;!w=UtvgJ8#O&3uZ#`3!d&Z3r{Tydrclt-J+u7~q^(3T zHvS-aregyW%bQsz%WiO5b3f%wql+tWtaWC6#;x~~eQAzj#nmX$fnCts*+|q}=atqD zt>+ofscgc@=?@kyS_$ZjDQa{2;cdkapV~?}@aEq4LcT>q?b;Yp(fKlw;{`=8?KhFk?r%eTJ3bB!1(ZH_3C49EOV5AxXSib1QET0`Wz`p zYy7zh1CjX*dHlFODyB_}k-S>q+DW$5=3%Pc^C72>6@+~Yn*JRuP9z%BeKZg38(S03 zUyRLn_3t6;bBY%N&ZqpL+b;30;dJhtnLr(8b@ksqS+cCeLj6+miYnnUb9qkO#_nI5 zn1p4xzQ>(U(2lD)>5_r7&dK2+B7S80p&A2hp=*a#=P6_`3FET+YzBGWrE9kDhxw?g z`M(*gR_qFvE0(miEVbyl_3%RL3(3^Rt3;Wkyq7jxklD0$;e+tL3%JW;5tfCMW+`V! zz1j@e#@bLeqHZ#n(j-pc4*ICOs;~yCb1*#9m5dfyEOnjGSB2Q>xvX+d?M9)evVPWl z=6rwg53JHqYmutT#Vc)}oQC^8CaAoHYxkuD zG=4m*(rZxeJGRceNFK!wj`NLIqX!bT5>O7I`&tt@!-}Q77nXdJVUe7+;l3wzD(fkl zpxUu6$Fi{Zs|r$jo9>XrJeA9E);BH=R6Hg29~L|?I~E?5t5QHzw^_u!q8h)fab#tl!8D) zgXcXD28@T&RF9pC&L=L>k5$Ic%daJLED78k=_dXg+E}_ws$b;L!hj?sL}!EA#gAaN zrz*)|rLVyDG&&VPb>3A~h}_Dqr3-l%peT~k#C_*gr=erMelQzHW*cMcLhWcd%wz<# zGX0}<_&fvgZ7t|2@2^q3P$Jb z`{chB+3SURWb+uCpB_x~=USV8PjSi75}+0?9Xu>x42)({&l|n{V;Lo0`3`7Lc+?hj z{mq%d0`=~8SD39F1ip~;<{oYP&jDir!aBF$l)M0s+;PEMUvft?Ea=dYOJdeW?b$~9 z;~r4(>6m)9pD)MMcex&BRzpy-*n{r39U;!IHHQYDs%M`+momEpBWYQ@4FoqeK_2Os z9e4CGE4Ix{Kz9NqTB}{-=JsDEfng#0jou;EdPTKPk9vTprWRNe%dR?M6@H=)l+cF_ zZB90vYV^C*?_5YC&~z00KZ!@-;+*e1c{Y^NNE(!CnaHzuN?AP45gTsz1)k&3Ggnwd zOS)ophn^h>MKm`_DE#K1@RM0zu}J1qZs6LALd5{wowLv?_rQ z`A3vKJ}cdxO=aZYZh-5t{Y6Z5+yMQUK%_Uta-@l=O&T)}210qwqqlhxrN?7Cy<%Om z?r;R!W^8@aHTR>UXdIN$2wE)#f5I`N)e|nGM0_B;Bv7!4yfz}`f8|Bm=sF{U8=B^; zM=hO^sJh34iiB>T$oAXuH~NyhQba=aGbYVm=d09A;FvFSIb8F6%5}}$HtkoXVmUW0 zc0-9gu&gB8+G5!AbD8#D>QL8)tKJb@ifSLLjIb!OY63U&>r^q<420B3|?Ow%Gt4hQ|SAB%$~zS0&$TM|aVpNeY9? z3H>}X7X?Ue_{F&58M_Bx#XNI8DBD}y`Boirru_r4%%fWsr}5J+v~P=ui@&V8P115p zkqZbbWse~q;pJQ|0Q1f3;Cvr-)TlGKmp$>7jglod`XLZ$g)=U+0ZXep> z!LoFLUX+rkZba#?7m2k&lXU8E01j)WL)AP-9R!VExolFtiaR*Rw z%J$oEi*cyrCfnq&*~;8^v-VH1?x*XVqMo1MM5`p@Ly1XluqlSsvZ|3b8$ghO6H0HAe9 zAcC~h!pjO~b<-2mRj|>Y&?=xy=wcD-{wsfxsr%a__)>}(!V+gO0AN|UM4Jrl%{1QO z3mj4L67Wy1_WBmN6}55Tky`P{B+0jknf%e(g|7T3BGbD2{+S zQ)TodHbW=(L zKS~w9{8T)XvfPnw^^m99^z^kCXU42V2D1JEItewI>sSZ&k-Hh{nSA@p6W$x;n*wak z7w42*2KRB7He=))nN{h;;|zUxB@QLW9`u(DK)(D`@rejv)9;j7FU#tfe)g&#v>|o4 zlNe@`kV-u5nqWUE?pWb55@?v%p{$p8r^peY8XgqMp0w-&!Bnqq=PTlP7V|G%V(5I^ z1}@YriGuPrhv%&;ovbD42LTRoT|1xh-y%TM7*;)o4fp9z4AHYBz#z!C)q8i(aw&)L zABxxEQElUnPXiDswLX0=3Z2IA$RWTApP2L6a)R>* z?GNlOx(~W=>zh(_)ooq_k{{05t%K$4`#hJRI-Q+D6aJvXWwF&G7~XG%LSm1mlEB1d zbbd*{@o)hBN~D$)Sx9`$ebcNDRRzN(W=+8p+J^prQc+uNByXHA#*=UEB+$!Z@L26J zhS&;Lro6_&@=8D=%UWad43tYg+|=RUs^6Q>H9u!+HRP$rc|ZG=&thR;<60H!7raTz z>#tDPe1|C(u_N;220-rh`4%49PqNq+I zoLYPQ#lh|rp})yjlk_ygM-3woi$Uba?|Zku>b|}Efo3tz6ASBoC%1kJw>}LSYu%$; zk6(TH{Q2oUG?5>2B|jCn5F1kiVz`z0`Q@Q@T}&#we2d~zi*Dui=_$!64?BW0)$?Bk ztqhTOUhJy)Vd2(NVt@S(Zk!m z3AplDZdRfx225tOD>|Yki>EOB9 z=ok3wLJg;xNK=%slLwMIQ>rH+SgYU63 z3e(_*uGL%Y$e(`3eoK3^c#f7akUHPJRN(>EG{axVx(1*4`ZR7F-cME!X=~5`s89uk zuZZ4iRVU~MW8lQP<#>CBVhEnLXN`mD)U9W~T+0_e6iX5+D^amgZVdWZ>!5gGzYQ3_ zU5vsxK99cf1n(2_K`b8cA5BM$>v~)M*?T%v&vA3STd#8syQtTEtcYN=VAYWEIh{80 z!eMaPjbd4j6fKk<(a(gije|HNI}~eJP`o_pw83t{>m&?|nYt(TI1A%ZpK(!o9&^@b z*5KMZa+C#!f&r>QixNb+V?4IbxO)e)}uw;Ajo-F z^Zce+hJ|DU+V8L%rUJ$KoAz6VeZkVB{>^Czq~^LFccF*hi!LagmL6qlBQ8#v&z`Ln zos0>72x!Km1xL*M=eb&IR!sg#)6}4mT|IaB6`h3)@w{9|Y%}&$nWUGhr7q+T*$s)T^k$ft=Wu6HF7G2= zLx^J5BB4NB-b)^bePukBo7QOK_of#_;Y!F6M!v%D*c)vfOB;|Z{;Ny8B0sBGg%ZMM z);XCj9S9gS4rx%>3#;90{}IB<84vs(yr9Q=IeecPuUyP7iINnz4W!PZAH!k3#*W=;kbg(T(UELT24x|xyv#|94T?AD6fQx*QoMpJ0lY$M` zAx*n1jPcqGxq`s2C~*Fk1pT{>lGzUNztPZ4xU$=E#GmW{u^a! zK@eK*+uT#Ue$&T3R|dV?m!x|4`UBCCaZgFJ(e&SKYOF$=E|UV}aJhD@P%28DlJmdU~b7nu>fk7ngoEw$=cH=%x9 z{W4ACb3mk9zI^2U7N~E=kXvzh1OYd&&JBBY0%LCR7#6hYU%r|f<6Oh%>u!C1nSV%G zOMIWsKKdGRlE-{R>}zVIA@!#9}z zT;IY&AyyBsZ?TdK%HP|6tUSOuBudznG2e{_nS>Gu-Q$4_-j?M>62D#_-^%Ot=R{eml^7|K@!pBT?p~k{Vx*N`Z^qNE1A0;4Xl)tTdEeNV zA5_l0*_wVeF;i*3OAOmyJg!EZG5N+ z?)=3&TnF&3k9+cr3pKJ*kSEz)@#QygA%=vl(Bb$v;r6P z#NJ0y-4Xi-?RKDP%RWpZiqoCGJ*reNq<5z<^wdkbnp9p8qW-kAVfzi0aB%T-3mue( zxy1R9DUN%W(Pa3DT^u^2LGM*v9pYbsPcK8``_}JX%N|I8vP+l~JVa#m>N$SN%~JCr z9bC`Ic)p7CX?202Z2c&E!c!EMRt8&=A1=t73t6j13=OV#zS z)Pje_EwZ4a=DlLU+I34l{`L1wPg;bWP!H>r?PJRR1EF+YvUTfpd_Ormvq46(^JzU- zZstQ|Buf<4s<3Uda@Z)weegZs~*T|d12aW(Hu%gr*;yDsIWvGEm*(li+4Uv zsK-gJm#1@T^gfO&SJ6#~2q;l|OT2&yL=DsF%#dyY+pE7SLrTLrsJ~Oqsw+MDz8MbAp*x1X*5Sx zhbPRMWnNR+JXn%wt)finFcP@=I%#DM#R_5%ij_ zK7uDs;sI0H2rQ*4?MypUV7=~AS)BK=96{|kitAPJ>$uBRwyO`wxE$olwx^nidkSfBo?6GG&3jg>!7?RfG9o0t-__! z*8AuoRod+faH7x582^;tfvN9HL-lXfV?d)>XE=$ch ziPCNU8^oQW1SrBW24cGrcY>uyQ@+sYKYlV`16o>PEtcNfoV*9A%Eu)WQCKSUT`4Gc zH*(FpFpg24_utaVw|Y`%S*p%Y0nMVGzpVpsYCLPSHxl{CSZ3OlJw=iHbKyB~r3)WbG(dSj6m2RwM4bH0qV4Y}`6YBI>J458u^| z3diJQoKL%z!7w1;(U;O?WE;(9OjdDz5W91-J42vZOVlQq-CSRTfva-zw=>!0#rS)} zPe}-t5HA_xV#A^O)jpR3*FvWOz7Dh-S(^3XCN>|+rSBe^-89dCaKCAZ?5UchmX#)K zuP5er*@%9;*5QULE2*gOEhDybUq3nT+<{9Z_>m1yCjh6TGAAEC9gSpC!3FdMg$$+% zwekX#IS!j)2Pg|8h2#c7zq+H z@b@Zo>zEkHxu~mj=?6_MaGiJLYouDiR;NM}YCO-&09#r ztuX_G6csBrqx-NwcTRwYpw@m2?uW{c=p$0{8Ju4T=p>!@m8rb(gj6dm7y!FPI$4_V zvt6GfArsTuWX|ifZ}mLs1iz_;LtafdO>*E)IZ)%cym0?v(&>#u^vh<1C#H^gy=K-U z_U`%Ft_ToA@~cR8|ASR;OzjQI!}S0ot`%r|Tp+@4Fj#PtM{BW@e@nS7t2YRdI-zj? zvtDlySr5Lz+0W0}s#Ur9Rfanh)^S#2qit@+(jd+26(MuU%=28Vf1Ru6MrqyL{L_6JcgN*z@0V<8hNI zrxlLwefLQC;P1|kvexhPX5US*LqCF)>71k2Z~66ZjqL*2O_|CDMZUpSSps-vvdghd z-JiaU*(b9EpY6^M-R&E)O>M$RTfBo#&oS}dxSTym)yU?#&k#l_Q~gaeIi3^Jxyi7y zHZtWUO4y3!yvAI&`#7ZBexr_l`^zEFIb#80{DmLgCXkWpR`hp914Ok#N9%0B-=TS; zkKiqtG`6H-Il3%lC0*!!G_+@Y0#e+M2|=?TrL=*Fro_JPa0+ol=sdG7cho%$^sZ?n zeh>q_jHf1z=AOlQRkRx|pBe;kMrMMSA`>BGz6q5+%}dxX1mXkP{=T$&MkCGxv_T$p z#+?%aVLLooKvyrhZsw>v=T8wT(-)e%P1emJ8U5jIcRvEz+{-IT8MYDN51inDe5(*{XI%ZCtIoTE^2{D?1YWF zcK{9}=QDQGr-W8YcpE+{ze}1hc7`qcG#CDQ8*TSins+%F7NII?FSMV3Pi7HfBLtZ= z+VRx3Nxx!}(Hns04U9;*ueGH0hoz|3JxZ8{J`i$;u~*nHeAyw6$5kk+H;ibOvT)4Q zdz%i|IG|%i;CrLX`971ua}%>MjP&|%jdsm1sjbjF`m1&%Km<@|C%AtvxQI<7aa9Y_a=7tDQ4L|G0ma#hq1R-C=xnJ zZZqi6f~YL{)eV_nf0N%7$6qA!9c)fKmAC-e0x^?X+3h5;65s%qKj*kO}X zBbnQV_HkO3XXj=;COzfaI=Xy;Zkw#15ih`r(7;m*c)NZtjFm#AKQbN?jfPHdOz_s( zXkxe4t&XSIV`{Zc=Ii@ktm2pN;NuuP3+qm;kU1=r#^GQ+UhoQtv1cjnJI*3M)?BPw zwYm60_BW6yaMMOWD`{%NfO2qTL+c$vI|UJUaj|wrvDV*b2pK-+{{U4cfgT0=SF5Q$ zi=bA#3!bE!rfs-1kYBm?hAq!PG6Mf{4=DYMB}*EllFQ?wy(E0Px1Xu!iOTlRcz*2B zmal9^mtE4-b^4~z@o`vUQNE-k$EseY&qwBw@BGhbPgU;xmdB)Sy&s1a1b#nYo=88Z zlRN80eJJoXJbPGBR&d5B;e@idzc=*pZT`JmIvMA_j&Fa|5fc`t)-={n-O?HV^A{9s zS%Z73#UisD90{1+{THfSd0rn|C3FNlc>WRZjTZ?z@42rxhAc6r5%lBohhQl>xXJv^ zNZrA1Kwf&IsGKLGVpiIK-NgEwz^ik!PZg<*`yaMd^6jh*^?8-*1sHbh-8}jKX#oh_ zCW^f=JJU7koWsY(_Y%2F@i5X~tl~AC{ji=y6)YWg7jStBl%$W#^d4ij^)r53ORlf5 zU{xwi*GE4m2!Y@MxyO4>o)GUB7Zf}5yCz1bd$Ll78aOksphYi!w|!#Tjo1_UR)x0u zvn`M1`~}=kgIu}%C-or~tAnPwtql2kDr6E>_b-0_{#e0+J>z&&Ea3>f!7nsr8e|+T zBCIE*{70c@fJ98m<$O?~G{0_06cNc|%Mcy!rX$YqhM(?F9oIKi>FS8uli8ImjfP1) z0}5Yt1**kcgsXnxbVLQT-{Yw8*LEozRwg4g+@$l*rI^d<@ewK~)u zNiGm5oK6HCXvMEnDC{xYopiNqc5Q6dO7h3=c}`5B?nj4j`)NF}Tic2j_t)qQB4dxv z>*)ldJ6?(!EmYY7{>)#q{T*Lk2iwx#XM=IL*YbB zipLwoLVWMBEO7?PZ|g|69X2K4)KDE>L+EH2aA_H|VG{k9+_18hri72~#pB3SZnJjf zHzhO8XL|COz>z{{$X`MzDFx3Ss-ttf$orp*d7q3cIMnw1n~AjO1YOwxFutbx0~uJd zgqUS=%}Ubd8nEB6C*`Vp=yJwaO(@u#+8f1om^hZ(xnN6S&%b1N(c^O@pcV5ucwU?J zw6(Hjt>#rCmD%#cTRSqu=7^rC2R^a5J^t(9_%qQ^!uwxa737$Z+JY4YyP)n- zNSIYGK#8yMOf}1UnH;1Ec(j~`iq>-@dGdqu`ml6_@qb8gJMI+Z3{1T@e$axw z$MsZQ&*hoAOJ7g9B4(;mZGO6B^{qxTg!JUfIngJ%l+N=8jC5XlC8m2vKP_%Tr>sg~ zDLfiEtes2t0m+kF-iLsr{3&*8cOF>sj*7ysGuL&!=iyW3AMtxZ+fyR4Xf6wZrmN-qnhIV~HHg9a!`iE98 zd>s>iAWv@V24{6Vv5jd!1WJ`R6Mx-PyO);Ei@DXL9L zA@KL^Pv^wG#g5bu#C@7|12pdfoF^^PGg2%FSPR~X`;vI$xhi}%eBYw`zumKgH1f4o zt{*8yzn8GL zmU^%u0f$ecU+h9vwwkF!YOH4oX7!;G3}e2C{(~PWqh>p7JgbDC!|>>Q4YbkmRa*{W zq|r$douI%Y0#WUe`>DyjT-4`YqMVS*aV4P zlk^nOdQd99Ns+psuvu;pn`uTKa&BHJ-e@H^@{=KOJ((@Nd<0WT)Y$g1`0|oTa>ZlH z2cPfOpSI8l;ZvbPi^?g<)xic70jN!;eNh8Z+R*YCcIIdSGRqC0&7 zpeR^{;{|M8R#p&dJ%vr?JrYg^b8t$P&`9%c_apvB|3&;i<&u?p768_oZLwb$cvgS3 zA|~qc%8!}BlibV#Tfyg{nSIsjTV!NRw=H6%{2NSEC{A5mee4zQrnp-?CcuuLVFnV> z@^k&g!^OEn&EkZHPEiOKSoIwhnihv&c>bI|-E5vKTzToK)3h;_-|r~8DNwIDu(B_A z8!CZp^axedDXE#}EwlxV!X7vKnOW&~o~?Yss|kE9EMG;)tgM#xHz!tdN?*ZD$!HV= z#~d>1C#4?+%Bq!EL|pV@-#L-e2fom3`JMI+9K*&O%a;CFT%H)@i_fTk-G?a^T~H-4 znV}>?QYVhM^})p6uN~cQk*#*fTuQe2h=z(Oi9zZ)`!~~jht{~tp@T!oq!ZNrc3})^I85p%68m{`c2>}zZpA$6IbV+SNC}@GPqol1 z@CvNU>YvcWpa=!6VV11mhI~(?MIZn0xp#~EvJ`n?WV~3F;g*m!S)X>>wl0GKtm}y` z1Nlgw()sjQ+^|1CdM7oTCEo1`%Y32hn5b3^l^=_&B?6t&r{^sK&>%kieQWEV6~BB@ z;IxmC9j<);rlMUU`N-a~OdC;@c1y}_C*Ih_fQ(9K{AR+g`x@?ek#$NMOzNkhv7VT= zzCWJ$H`A1SIRbhib#G=FY56wTrDE3JN3`!+sjx%e9Xh$k#xi~6dS~)Js>RrB_76fg zsR812qb|JUc#2VC@>SOiF;oeg7j80p^e84sJ8{aHFt^7DKX-2p5KkdNoNdv@z^bk%Xxt-T5oqD`{;jaweXus>{K4!b(FeHfc+>|q zY`sEPqvOkv_JI(l*EG^;t`IOV;#V1lCTA|w+|8a)|BHpAtXgxVe^a3eCs{t^Hc4+= zS8o6zyK71qLc)JT=Fj_$P@#P+j%uvPx?z9SM@d%O5>3wb7e!yoq9ABX+04$jC$nCQcg~9|#>LgFPRyILi zS1ZE*9U5-AFn1{E`pMQsyG(*KqWL&09ks^tv=JplLZ_Eqn#6?z*+eASzNO=&;%{I9 zSQ~<&Hw#pZp;b_vng${h0di ziIB8LvBR=L4Z)ax=J%XgJdY$-LWogC97XV(LBWW{w$MKw6pZePYW{N2y7l8SclDB;BRgD$BXYw)?$_Z1}8b zt`6klwjz^8Xog)+cAnC?Uz3NY@2?p;(?4TN|A(=+49e<_`h@{eN<{?(q*c1ROC&|Q zQ$V`ATi`F;fJ%duba!`mcXv0^`Cd2RbDnuWoH;Xm($U%MeZ^Y8TAJnPtqRM?ftcB} zox@++rX4LUE{>G319!88gYdm8k9W;;KRQD0@UQy6@{eoxTQO4~mPl4AcOyMdL6BYR z#3b`yBGXBx`MuNJXmM%xz2ySgWKUB{@Z0jDVK?= z-wbTzN>KI}V&G$$DxSypTWP^)mCa_IjLBeJOz2tanY=sKX4)USp^Yv572lN9XDJ_^ zcS-Y3NMmdp;T>f$Z@_-eqodi^(7@s~`%k>m$d)UV><0FZkBP>Q1Z|VvD{^F=Y?PTR z2!6Img0FyMwZK6pK;|FJ$@y`t5iC~&Lhj|xb1r6<*c6Nw)hc_s|i+I(iFsG^2S9fXP-p$?VHJ@*%!qZ)_&-%tz6`6G?y-bXG1hYAs3A~d4L zDs5v*ByV?8hZH+IpM3&}BR;s{e?^-nt;*zV_GW7oO^n7K$4>qPJQ6*<5>*}`(w?B- z4K}^ikpXJloJ^Ad0*cgvaAOSwhiO3Wh7;sFZBb%$x<2{8*5J zmOa@?nN-X&0Hq*EO?wAQ7p zJ~{y}wnl|ZQ1x$iV@Dcs1b5&(6 zldlT=)cF<*_F6~nu!EG5a3Q;_iIEmrr*f25DXL?mQaMJB%D1nntLlSdokvz5+l2hy zCp9<32(H8q1xuxK+!Y2H+!!2_*!tI(ww{H$TsV5z$C`O@JBc!a!8LlvrxN04-SCZ{ z2}7-h#8n}y$`ZT!a;Y;Q^M=#);wc25Q$D*L=Y0_qB}z{_Z@1J8Op#1ja0x9>`i+o2 zYdO{mPJ3VPzq4V>T#-kr4k6@LDLmx=cW+z>z-5iG8IS@v0QfyK#YU{bkyy42? zV4Jp2Q=igfaDbGT^+eZ*1a_JMp~bJ5mpODKI4$iyp+B62to4EsCSg{GQt`e7kHZV$ z*<2vZ0k^O~L~l;>Dg~ygVa#iPVlRg0 zHc&l9-Q>MaRo$6n@o}=v>R@UMx`qd(J*#*;r@bA4p6|0}k^VP-C zE?Lp^J;m2W>1>;%Bo9Rb6jx7ax_Rm|4Lh=%#x(c5MoqaX-;V6_nK7o;298n;w}!W-}(mxZee~>z1oaGxL~u( z8*$G3+4%Br%H=N#R_nFSCqrq0;Vpw=uZnC5**y;|Wi_UU!=4}elub%PFPYLym~R^b z+&l$OIhUTY@aXGVNm3ZK8Sc&`2=|HX@{nCB=iT-nf~S#y0p6}89l6AI>E zR8IXwRcH_F6TKYLg8hS=9+c=`Pk0{-pR$`dwIvq8bSKb3v+hvM~0yi$n)9@R*bfz@qw@N!MET zY25E7sL3z*Jlpp#=2-h&w|8Ep5s=Tu_NS^#Mq!Y{{OtShC;nxekS^g+%O?dx{?Ojc z#&G~&c}h&C$t$saV83Huk}f7NkRt(xU!OU$m7>kNl!d+QO>auLlG-@F>FRYc)9H{Q zTZL?6FsC-j5X)&!?ox7$W9lQ^;I+p$Hyhhb64=+7J9)jsV=y|cQ>NtfpF7h;z40`a z(SPGtK|EP)7+-T#nY}pecRvg);aHjM@Ci4G}At4XRV)?2+d^>lqKyI#k) z=Xug`cdpyvIT_d49~TKdMNOpxzzeI(ku(tm*`QUcJJQf%GmiU9+R6Oyc8n1SQX#(} zZ_T%sN|vi~lHp8c?3;_r(lNav00TU)Wy(=6t7sK+O&qA|qjS7%b8r~n;g#IHTxIq; zNIlz(YUKl)IZR9Uw-(;g7!A5zlWfQ4E=B;gP*60LcmVM$+8~YwV5dxG7PxgDgBx#v zZxn%^lpS1MyVsEZ3HkhV%@`>SFg)4&5W|&qJ=NmaMBzp&8YHZLDE!*jwVa70a>Qr- z;+^lQ=T_Y<((&e88TDRh09E%QZzZNSLlG3A^6V^hnv;+D=%{qJ$^l~qO72M><{IoW zbyviKvMF~3cXt3xNo;+8N{-67)V&KxtIp1|WG0)@toDSsrjZA$S*v7EfYt-a;I$Pk%{9fYsI9XJL zW?QeT)=+9*&DDCN|A$^PN5i4i*LI0%4YHElf-M)n^}E$zmRkFM+#GqVN@{^XipLe& zsy*-+0AWG>3(6LwSs^n{%C2FTZmu(J7wtrfVNuxh)wRCQ{x(n3Y!tu_x9VkO-x(Xzot~MxC z5*1=;rO)N5QI4^X3`bpqU%&Z}7I(id$NRA5D!cQuxHsi;m7QUrgyXy-TQJw*e`Dr| zkB*SvS8WVmIEfwI-li>)CJ24*`}CPP{6ZJ98N3V7(GP%K?U^!jG}m{=)9b-aGc63L zBlzmQn!i3?Vob|qzF@lgon%9SK;-E3J4ojHSxR1z?S6BfccRLYRG%zgD<{cusfr=K z(9kQfEY=y?;Y0pYSsVYbSmEhx%z;#tM!CgHv51_c79#dUM?{3^ckir~*7I^{Qh`w+ zc#zW(dNmhw_DZpc$8t3Wu$P>Ji3LBHGW6*^ZcZgEq1Upg$L^e2*Jl&&5XbPDOY8z` zEBApe|AluXjaQF9ksIeR9{fE#vQCUBEB|0M*xXmMi{Y^6y8Kg^xx(SBou1aGK$gZW z^L76exT14i@?|(YKaLI=F=bXb*=(rzxx`H`d#NTrXfl`2VmF^k@YI%7rdKx zt@#{(ot!}vHbhD;5(SGb0SCl@4pH(@$hraT9oxIv+UIYWUIQ?whZ2`IRRZ6P5-)Y8 z)|B|Y=r8zK*kIrjY6#D>!)oQ%ou6bz`8rOdi^iMOF46fZe49Oz*Zxmw;kUy!gi@f0 z=n#oBs0Jwetfpe(t8!*SCJ)yVfDv9b2j-Vb7)Cc5IR(Y% z!m&>NP?RomG+)q5O4_vaX5kMVc|?)tYLTm?hIG z@X9ui>=+ap0ilkOT-~n4N&KAHM+2B#k4meR=xlW$OVI)p{oqWtBPkH^-XP{MD^e;T ze)|N~@I}?c3tHE$bRUE>nLGSlr6zjh{w+Qz{^{iJZ}Zx=wU>ll2&}81<>y|n|8D>h z>A*SPpJTcXfhpuaoZZEO_huMR-*B}Wg|XKHuL9JU!jbRtPiajU3T>Jbv)`@tz`qUj z_}R!dxOtW^tbZ~5@&~8=^9kIG$rXM>oU(TuQ2Ru$+mgzieWLpX5DALVXeZ~5^dv8^ zdERTQaU&W1gkWJ^j5#DD$Mdb(nDw>N#3e%V;T@{qQ@PXNG2!e5Wf=o1jaBK_#m&V<{|k}a?jh_iq3!cYIs%7teKPa6Cb&Y)|o*Yavj z)?&4rol}(kGdkM*!K;?oQy94!YS*E2y%a%QL2-1$?f3j~608uN*}}NHBia?c=D`-LyR#q%dps z{^$W58pAQuukV%Zh|KqzdVv|)-@Fe}by(m;fm7@F75h(!kFNwCh+sT-#rMI>f(X_8 z0w0Z6?*i4mp}_;`3~KW^jo`h!_u9Y!c{#9FxcqPmnR@V1Bm%<*)R&g-?Em-PCj(28 z00nd@l?(rw4eB3#A|zT~HCAS5-0yS#l&ae~;`(L6stHDcD(3O`SKP<~et~}GcH3I- zp%3Soes5Q>97cU?_8`aqhz2!7F_o~(Y;k^CnT%8$&vxh`MewrbS~x69Lfd2g#>eR#96*b4a#dfs3p zTBQtHtaLIP%>D`BhpZn`0IbVkHrrD&rOa&VG4OVu9_XWj*ZMa2e#`wFD^q)S|C${< zpyy2Bi{tIt*Z!}mlONC2T+*slI6VVFZYaXRH;4hR^M)I>XMS?niv6cqC87be4luNe2DyjwMi>Qkvwi7rSRw|%_!2?hs7JeK`Qx&cB`C1Til`M1}5?oe69 z+0G&Jc)^ltB=gj}q3X94E}n-bjpam#x|DkANqVy74jZfypJ0Ka8^Ok8?rEI!4SUon zQ2P?`xCKbEt8qIWOtg1N*wmBK#p1OED%o zr3%)~`C8o%T6gj~X-i`3!mn{BXMykGEdCDohw(l~Cr;Knavs7wyk5|es^dqgVbxS6 zod3#Wkcbu33Gd<77LwKtdsXBN96>bf5^ewNQcs{366t}V)Tfx>Xb$vBo{MKSOCui5HT7!IVq{1rg^l5Ew}WH=a@N4;0>h?P zB0t}vXy(+C7V9=F8=Y zd5N)=3r*-RK_3~0e#^@fYQB~ufSEt`UnMgcYGo7o$rTkY9G3ZPVL5|wvP8B`Sa3E% z%tq}X3;!k*KZN9|=hgY&OzG=NFk=bNA3aAKtJb(JVt-gxvO&}R`tjcDeT8lu5-F}8 z3drfU1_Fo+{`PBd99#UrlDOuM7!vD6nR7_VQZQ_It3{#HN#&gu0!}jXZNYD!<&Wh3gM7j7zqZY{la_ zWLWSNvHUvC5^+!w7t-~!gJ8{A!3+B-3ED5&IVUw(Huv548#RVQEbHSH@jU)^ITR4d zc%DaVv}c-zoL}3O;={@_WKuky9R!qI^%v-@mG%G8Wu@h<1{_g(d6mP#E3T7q9S;Vc zPpgnge@(eJbwIuye`MxRY&i2VJ3V(HEG!0jiM^<`VamL2BaB!5OcpDGRuK;5dy5RX z^yVS0=y!&aC-e3?Tioc_*lh1(59i7&Ke62%L}f9V)Y^(}Z`jE8332(cp`vTd*KPevWXVGGp<*H**bE`IZe0%AO3 zwt0NMwf^%u(PH8BYoQd6)SD5?@ar>JqHyQnhPo-bXFvNK_?`*= zppX~ilf{|aoj^^Kyv-!Hye5ZumskS1*AJqm00xzUS0l->TJDC=x96y)><^a>(`9mg z)*0~JfX2{Hf&LQxjSwoZz&?su^WL>sR)d;Fp?)n~^ZzkdLT$28m<(P9OXmtCQ-gx= zFDL#DKG{I6Mox6t!j4H^K-ee4Z`3t&DT|KL`AZJRlY0y~19ebyL_A9dsUjz_=;71p z+G|X;8Yes!)0;N{praT2bH3nhaJ;#)5IDxjKWSq)zZ+X+v3vD6lGU7iyP`}+CBUW6 z6LnHFgCqh2RL|_xSMmSMfJ1is=A;S?)Y8O<%%0ySXIzlA4e02rSQe#X!gHgF8_qR;S;R z)+)&3qFE{Vo9(e*ODEkiotSxAT&Vu8+C&0S3VUQ_#jrZ^Urx^q3iDvOw z{?m6#z(%zrFq)@`hm6sQnxQE|Jk%RQfLS;)Ca2GHcSXX1?C|N~h)=0V-6kHZa$40+ zBe)Lqu1;tFqwS8?x30+?!D#?$<(kabB3h7_(l?9O!0yj^TCY!CC03?-8%`aIlvn!; z){Wc#xm%TuyliGw2HlPJ&Bt;omLMELg|R<)a{%WQ{fj>SgcDiks?rGPcwRM3X!Z%5 zBOk*pCfw9x)U#X16a_%AePy(!t0&riY@h3Eb&cX_>(nspUl?PzjGsOBeHKqBZoq|P z+JZ~m)J5A!o7Q)6R1td~@RBeKS%>oPi{hSZlR6s6XR7*h+6c#sH;i3h9PN!g?hHRLU$dO6ebj$8^qgm- zKMCZ=91>wFQe)!*zne$tj?+2(&Ro??Xj8{#u}`P=3TGmeMt+#I*D=wCMm9Hhl{dj% zvA(0~2C;YpFNdwsZ+V!udcNsNMX}M$OczT^X5?5zO5H9PS?f|^7ZOnx>!khUPC5G) z+uh&gc+%Ej-;$DG<)oH-E2ZKWsM0;we5_)4X5=Zq>Z1`db64FR9}>kAt|oAvBMALm!xWR1e|}>DTX?sU-QLQCjwgjy^~Rg^N9V~ zzY;R$j+bjekj2t@1%K9n|L2qWwb6UEjWF8ywUE`c77~u)Oq`)OPWPXMzt*&xS}(KB zN@XSGs@+GYlL+e(@{)M3$arvSsTEB1eUrW3;g@e{Eud0}v0f)35LVWbDEy3Pb41o~ zqK|d?Pv92JqUMWpiSsk_Z_|e^cfRtwrC^#aU-V1P_s^W(fvvcogzLx`f->*{G4i)q zT~^Q#xO)+mGkJm63r3xz?AI&+zC8ih3~TD|OMB-3I%_lRePm;~Zh+)`h;Lj(y;&X| z344JhxP9N7aAfImPOE8ck9@IQkkb;+YB^{zi*so?p14#iGatj<96d1cKW%ATq1*m@ zz+(yF%BFx=f|D@p4VEtPHIQWg@p-gns(pcN>vpXVGPM$3#;3m^b7|?S{&ld;%jmhj`pkmm8Z2f1P#pD{k>F>2k4s^him8OX6 zawZZ_Lw&T#INU`*3(ik77p!JKC1&p3!)yKpxI;z-ND?oYB!hHdj45hfguLX95M19D zUp!j4Ig|BBE?Y`WV<;1odOxlyqlcsDI2l1_yN={DXFZQrrgzZFqU@rgMh77+!t*yP zx^F+a!c5-ulxde7UH61r6xB>OHY4DxSD}WIP%b~d)qy4leESLY_)I>vU1D_Ciy1!4 zY*acPs~U5u7-;)ASY5HSkdtlRo<3piM%GuP2l2#ZpFKQGs)i$nXriU2((z7CmkPAa z@-Rdk7C4ct?8kE6^3B?dbuKUaVFK2IMy2@2I9YyK_TKJ{bK-=)dNOA}ubM4~!xwvO z-7dvBzRTxvN1lUUuzI|uUjC-7)0g-X>bt5bq?H+ep^MRBFfCW`ONlus%hFPw27gmg z&hNQ{hJp)?xIERFcGOcvP!+7wGN2vwWjBuBa5WcY%o4a?ArYWwd~7k&S6SzKN>`7B zSq07ow5~EnO)Cz9W~=ira1RxyNjO{E-5UTF^m(|u8eP7d3{qyM{Jalo=#fLefDyF4 zvQ7&hG#8zwz+!TM*ks)$`-Ua)_~I{5b&#c~IaX9;KoBJy?`W5-~gkB;rTQiNtyccm2RqXc;%M2_~VV z`D0%4IWe0LVB76XraieVk0pbMBKc+U1iU_VF-*$kX#Kn;W%wC9jL^`d+w(qZ2LSvg zbGe@=_bI-|vVRbghIS7qGwTRuCD1n+NMX?esoMBq-}@=eG7W2BMomkglGDxBXm~?r z$%fvF!(!&stA5dC^aJjAE6<*PtCTyLATSM^5fdi*XJgaGm7w0+8|hgC-g37`bF{T| zaUqWF{{)MRMk9t#+Jf*R%&yXMFLr12v9eWaG<*l9TPQF|p$GUbbT>+QH$Er%;&p!= zlYFs8|92gmLE}MiM%o~a4u4xyeyDAy2O;dh@H$A@lPUlC@2Cl*+5N^54Zl)f_AJpa zxr|Br2UQfQx8RifS-2xH*FJRMF}3{TFBt9_i5qzmBi3zr;u> zkIh^df5q^428zaL5Nh&`ych{`JEotp?)!Z{(q-Za zRuqE_?peyS$qn^Vmz>3|Zc$2lFeMBTWDgE71U&JfCztOr(MV?&XZ!E=XHr{&8Mn?# z`Wu27P(T~3X2|LXA1cOUYQ%FL2b>(nem(LJ+e$F!4OTanna@=(L#jN*qAD7{H01(K z&r-h0#G7?mmS@fXzNn)o&?VutFoApcOmG|w*!gPdWKB`r&Tsyvi{^iSJ-9`8@%s0$ z7oR!8Kvoe8((j~y$sHEn(42#G8gSQbl6n_6Ad+(j9x@$n@qYPsA{G6CEPiQ>`WH@r zhUI_;joA-Yi++Oh+biHtU4(w(K{<%IGloUA3W{-gVD%;0& z5{uMye@pX2Ca}4zAFuY@{x{MhTEjxo(xcoJR(JdOdw9k(zYB(Pr=C`et21)y2oXgr z`<+4LOvNn1t$~_`ae8|V+~2Qbh6X%wmRtdq z`nC5cvshI8#-UYS@|}>x;Y93jlXGm-=G4^FH4-1o1Pt1n!9R82Q?$O*xKY4)@LIwM zF&D3_AHg<=bC1N%j65^{nzDN>%Ni(+F?9rEbF>~Tl@_PJgw2dc36X!Ztp|`yqPx0d zN1Am=AR8?XwT7U3czPWyp+g*5B=Z121a~YoxrUPjBn={v{ zm(6_MGte#8*uc-mXX>Sk!w3TUo-BWJ87x}$(l<|@9#w~;0wW!#JCVl2+YY*z0zHce zEe2&uTM$0tW0cj1k#wny+OgH2O!ftxEgdcqoyDBc>m~VSo)L{sz++dxx^es|bvlfg-ul`#OvwROsI3t`^bQCQb}7MoK! zzlhAx@7py^v|1@-uuLM^f!V>@3ck#;-~Df9@smM=1c65}1Q68FHUIK%&38g+&DHLc zY$4{q41+<9Y_h3}gk}deB}#R>0)9crLs&Uq9SD8sg$Se4Z~dFiSNY-ElLdYlsN`1` z%Kx=l&rhQ3h3Z|S=O@Tco4?CSl{o1Pm0;0V=jFFt)91X2#}p{ZTuG@C?4rrnS<^_`roJ3V zD~XgcN3~jo4E$geW{J{Ip&)S1QDTvO!Hw(pNo#u#O-3b0I~l#r4rDpv1J%~M7$)n{ z4_KVNI%$34IP^i-K9)NwVTn2jeVg+@Hqm&5{nxcH4FONBMMmAC7NOYQG@s zgu;@c?2p&FvKAW+sdx1SJ=&So!Gi>m#yP;@SC%!vjvmap-x5%%6%Pv6X2dLN^V}?_ zOJlF=3u|%dpEx*h#^}GB)u{19OxY81pSV10qmFwL)f^$?fK>x?B5Kk1ZV+hG$`N9~ zyWa?)d)GqUV#$D-$TiHq4S>=FsHn=Idpv{A_~flWNgr6$nRIpxb14ivy%E2A5o%!e zAEP1OCr&L9Ca_2TtBvp5DO(xRsS+brAD~su7KDZ@LXCzz%Nt=a71p}m8$(?2%!8fJ zsS_WyomqM()H}z;=JDPnW8iW*$aU>3Jz-9;%_2Fx7%zxVP>5YsiQMGF=}>I(wZ8Di z-)sWr`F;V%AbHkzM`TBdXh^cg~ zcKu>fOf4QU#Ts=tezlIdYB42RT<*K@?a{}!IpQPe;j7s7TEbf5lu7cs&K_^8z3leX zP$YTIljm2#6nn~F4T8y(kb+LTHQGqKrHht91v^4C0_bV|1b?Ea4he+s{#Bd4+}pDk zvBrE@=0mxSSTW0AdDb8M(nj*VxX&v>fbD6xU+W<3?>(R|STjRF@fIWRetWIyuu}rr zUyX@cXgS&_d0!rLIZvchVr-L6>1n|LrkhFNdlYv+oItS96Ufz;GL#m{)yY0| zw!wsVQW`6yaecF7OR88&-u2+HQlUqzKO;$aci?&&0vs(h)uvMt2EgZ~btAzokU`gq z`MbFA{2J~&K`PN+*VcY-7Dcsol6=N~cbc9FbD&W0-C430HMU=Ql&LXrDJ5#o^)ghB zg_odcnWJmmh zbrS$VmgS^t`oiKaS^$aZ#UNAa1)(3^5nsdWFiWu7HeQPP(3fy3;RPz_o6i*DS`6`V z9nlJVuaw#KxIlATGQw6as=> z7YwR)L3n`&qkdmQ0>93}V#*v@`#zUlH(|Xz_t$u-z;8G!V6Z3pYHz?$HP8w@E@wdC zT^c4xx6r7dz<=`mFtV!OZ^;(uw!UNJ3lIe;NoVt#&DA~z8Al;ix?4>KKJ;e$zNqL8 zL4DDzp;Zy*Q6e;FT#y^Xxks>1b%> zdLFnTb4XWxaJG9QS;U;b2=Q`BS<>n6YGZNhWwr^NOIGDv%61PmIbU5+_m@oZ5fgLX z!FUTqP#NVgT&VF#%W958F-De6ng2+okYDB%eUa$oo%}{GINSKj(0ROUFG0F=Fc?&- z0e>2+KO$%)l>_<9SEUE}OS0;%M`Z==SllXw!VGONH(cpYw}!N_P4}Ue!vY ziucxXyfsrI*l`CVPxG0~QXy{)j!SuHt6OQKUsE5ox9!Zipya67Gy1QNf!;h)T;r@q z1gW1Avev5#_fGh8IQ_a$>#uToI)i`L%aa+!{RX?|MZnV*TrI^f_iH6u!+l|21(=I15b`W9# zrXVJzyIotYI(!<>??MI)ESy@yK}h?%L{z7F8Qy@8&iw|vP20~m7t9_}Ec~qt!2#}^ zCzmG#=v2XeRmf`i(C)mR84ihvRkR|lmd_@K|F`tVmytZS@XNii0&Za;fVJzgWz3>0 z{aw%mkHqd?z=@h6SHLvi>B=SNQ!aoG8=@M!_oN&0D2neJR0BS;9O}cHBlu0d*WM|PL{=atIyLOvNm)Yj+V z(Eyzp{2lfn1`Q|t734o)1h^OFHjo|uM#GVvo5@fHwXo*cx4Sz&ib|i@(um{*@IgS+ zYJ+0wo2VaRU=i>BUys!0j~Gf?xM(xofgrq&&RnD1T4gr$VUK7%V%7O$AD_=_r+ zJuPKY=<>Z9h}P_$zVd^9v7C6H7yeip5x#Vp^5+#2&UPc z)&lVIh*0|e8z!NYXFHLu2+v7 zpJGox!UyTN55a!8WSNv&dYw3I36xy7S4H`*mu2~2l!Wj~G&yawBZt>t7*dv@JM3suh^Le*fu2tv?e8N)Y6-uL@oVZ>Bg-Tf4ZQVGW!2f7shO z<*?lheNkuk^J)COgrUV;jTNptybL5$iG-59#O1I-2NHPQDubdG;`e=ESakad9=Fr9 z9p{Y18L*T{)oI8_?SM4Wg#e$f>(>RA*V+z?-`f`Ya99)6&M(}Sd>HFZ>bADz((Et{ z=5nK4JpcG!KiKI;y1Gouxbnn5xGa4AEhupL0TNtP#^K@(7B6Br*BB(Pg{fuJjs6hvQx;VGaNx2M7V+hL zP`NPoA+XJvmC!R6+G>J&OfDQ>L^=AMutFrWPBz5;;s`rkE=3D&WV}kL=z2F6`OEEW z;Qo42u-G0{X#zCM5xeSDr?4l%bxmfDH;2|!a~o^{M<*3M0e`K9p#2%Lj{bFh#=kA8 z^cF1EGC49~(AwX1Y%fxvb}~_|EBD(|cW75u7n=Kz56ROp=2`*Mzeb^TK>X=-cC0#ob|7o? zDqj|#+(a7mq*@=!CsAvupQq*lJSW0>!%5b<5Mj(}IJTZI8yC$F zoT7aHo72xV7z49Rzsy#rKzl;wOdI{a8xpxpsV5qaD}oLh!`Msluj*j00DA|9t>wi3OiQzGz6~phZqfB5B zb%&oil2EtIOq~Jh^Xf}8jSXg1?~>8K@@b zMt+;rGj^Rr`X5$eq_I?~%?}zgeh_~Wa{cK$2Bu<+qK6enpSs^(5-bB|mCh~2A27Oo zCk!U%{w4h&VzJ3Vj~f+aEeHt`+X9Nd`WFVZ3L+1|r0Rt!I`XJQ?4_em1W2E|INn$u zvd7x^Uu<|DlMVkW-E&;7r(le(?_i2c+m>UZ8ntXL4RHZ`?`XgUck0SQpVrs538T6! z$0Z{@xnDd~=$Armqo$k*rd1j${o7GGM3*%YPovWo`2<}+#8XKG3f5Qf0ZQ$r#%fe? z(p6G$b(d^wopv~w&dS3Sx#=5gx#QfV`1Y}yT6-R96fEMqy{|J3Mw_KPaU<8Pv}rNNdZHw4@kge>Xrz##juhV= zSHta`c;k68T^yYmUG=F34>qdlBtDk|`u_y$40eeCGbxu*FqPC*6^S?=5X4CC4}RJp z6!67NVWp7ZeWmvzSM%2UPe)PgVKu`G%!e( z2rUlcxxX%AF1cS=C)8zmTur2_a z*a*G_FbyhD9FFAPLT6r0^Y zgW={|%}}6|%5o zyuYqM&^kC%e>JjU>0SE^xVlgxOE8;m4wDnP?Y!N<5YZA(MYy@SG|w2Xbve%z_m1!A zi>6nICgwv0`GXLLz4(d4bneLQ_5mHKvRT&^

Gt1ff7NhPIxVI^XB97qeY6r(a9C zT*WBn0Ce%$xP&XKR5wGz9bDutQq4_#)bSS^M;nY%8UAhfd2h@{0x7?Bh5a>g82h7( znHPv%_5jW(qO5f&PE&5-O(}j7q@{SKTDrLzmYYCl$N;Ek?AzszA*mFVcX`DQ{Kz^s zOGaPSYN~CgB&tJ&Uu$j7dpAD*au~|Kykc7lz8%(QHXFo_ zpKVMgUyy1RLS=>8VJ@0^c3P$BHlJz5YmZ)~Dyl2kNtvNL%=%tfr>^`(yWCGE=-q>#5ZRG74k8WsA7I`awAFUoA5 zJvO9-4Cq|4&V9%lohhc9HlZz}6Z8@W2t-N;GW8FzVy%2hkoR2 z%Tp&)Z2hA>&|d8erG7!83mn__ku#u7Gp#T&s?z6emlA5n@4{-l+rp(iC7BYNKJpTOD`t$}=lnS}s)BuHy!VA#TK+lQOMPNO#7)SbY5D|4WEE zG&l*!wAV&sIZjVeu9h=2n^z@gSCNO?6Xh%<05n%5{Tt*N8x;(yr&uYk57gw?ROQf} zltoQWvo8@rHSG+6n;*l9lh~Sz6PHD8n~LNG@pH4)L7Hl}_CwgFik)g1mEYR8YsE6& znKXKT5=Q^mr;#%0P#}0`P$it(rTHU>&&^N>Zt$)Y=y-*X)AcVX<<#Vu=jaFNo9H+7 zBOMM*SINvEeXkkKVvm;`Vkzi+b1LTS6IB6xD6~cK)L_Tid0adK8_=I*g%g&;PBu z2q=Z4p7~t=e@LA_cV`E~ZeHDumpw{oLC@PzWQmK=={ci*Fu1wCm^`j$WLg1hOY!Zi z1Tf~PNtgiRR|E#dh#zAk4>%!(26RJdLb*l`x5*A!OqJk3+V&~KV^0?CI;HsnL)>n0 zKb2bSQ^-aVkz%ijhFGtmtgk>E7rU-iO`{u#FyB`R7uPEcx4b~bf(7}R$NoV5QWLA4 zCjv`zzviY&bAPb-=TI~jxVJ&@2`{<%iW&@bX#Q~dnlnWZQk@th2k(Zv~lzmvExzWr%fB!WY{vOJ-^s*uNH>IHfFf_3E zSy$yV55>MJwAfGL*Q$Pi^cGt6+1?CGw$3PEUCI(Zd(;0+EDXrE^@=})31wglVzz); zwAo<9>1!N13v?>=QlWA9dy4HdVltoNl)-3VO#JcYgoe=Gh%;HXMiH#tj?z1m5uRuF z0x@F;T|qJaBee>nSjEVJmWLFKJVF^PRc53QQR5q@7C^akYF2!JGcvO-Ug}A|4RUfU zw%g(tI#-MjQ(!OTUuRt%00CaUQo)Q+<%m02Ym(7y=!bP7!mm{eZJyChrpwNRO1lp5 zf%of(X7q>>^8KUD@0mMbBj{#T<-T{We*_2;*c*@edA)yi8=?r|uvG}U$j|CVhfxUq z2lA`%Crv;?^%~YQu7Ms=!d$SxRDjPgi6t<0f$5f z&)#IZlQfuTp|6|n*8$EVCV!?Jd5wY}g7K#$$xXh1zBio)#$%>lyWCGM-xK*{@@jxz z`V?KyP{cCuB*3zy=%&1m&*l1MUxhrC?`%3OxJ5MsGM=f}8 z)w<;WG=HFS+6ZrqJOmUHQGHk^XRz=ml(_Y2H|nV5Cp~3p{4dkN_Lbm zlKb9E-;Yg_zk4qCA?rMom*c!gw>Guv!yYgd1E}33xYW+t_1_yACxX_J{-SA=5Y}AH zry#o&F_UkEkuC|bF|d^6GcHL;9GfL_rh@r)SPw`fY8sLx4RohZ7luOXicV=Cx9 zZ@ExAwt*(jO7f%*5OcHpD8^+(xU8@JUo@}0QtW2wOkE?}887hT!DWd90Ivj)g`@!w zs9N%0VHw2M>dtb7Sa=SXv-EZ0+RK3|$JQ8rxv zfp4=gHkYBWqxY6Oal-jXqvp-qUO1Pn(wUyHj?7C`nlBjIf+;uzGNHCkv6jbDw*(!} z7%F-(w^*dNSZ3IaOnyYhvav0%L6VwoPB_ z>Vg?CbK-M@Fm<09vP{Y$Vg#5_Z0df~yE0jlC3I^aqYk1j;{0FdY2#Zro4RWRPP6T^ zKIt87klo2MjtECr%*c-Z+RACW@#*__;~pL6L9RsDf_)0_*g2&rg59#pU%#h*;mn^K zv#M7z`DO(gYU^-C^`Q!~i46Q$0y98T2X&3ChAG)FAhz9AXaubbM{Sh(ZGm{(EuMKh zRl8y&S2B$MU276BftOr*6IUC~2TWVz{6a1f}ZPCvUA9)qLw={O?+S0%Jw$Xe${RFYqtRoTs?xn#X zmP3aZSFOL1=YxTV{t&JrzAu@Xu5^4n@*h!lgaqZ>3fv@=Q#BRI&B%+zx9i#d)MSslZ3E$^gG|f6TOCUKU;u*hR+d1@_FCH zMejIqtCW{~ba&>GnfQ|%W=-Tw5fE0(Vif7Vaj)8uG!G<>y1S%Px=U~)E!`#E-F2P~_&?w6 zcYnrUaK*cM_Y*7ToNFy&CiyAU3{z-;w#n&1pBRhj>1*X57Bz9d=sx(MY>A`s9Hz!W zG29eesl%}8Vud@x-(ceRJpPNdZu_G5Y!AU%h79EZms*1vw8F^*HSSJHkBB$NqAB1l z@;}i%?cCtrE2|jFwbqCW+@W{_W)Z$+2p&L!{3Gx8@>F6ieJI=li zD1=~<0gx-}qwep9l9B6_frdK~N;SddP3=jmVkZoO6rLY3R!tvjtn{+M90-g$U3QyO zzb$n2?rWN*?zkj6weyH*8u4;kY!qCYfU=tdFhymsSIs%dnu`FX^(PJqKN!^gRg=J@ z<1O~xi@tn`w8}JB%H!e%?jQJRF8uhwj`?&`BAGpMf3brW8A0VSVLfZ)lc?srHjYRz zP_P}J&}V|T(*ykR7yO5{n`H1hkV7fN+mrVIUiCk4gVpRpjPE}lT?VWK&!Ak+AL^k( z6BQj>bCMuDo?nTrY=sGb>R1=r_FBArbDVm}^odB$k#xIbJxaOb;_%oC^vtLXPVkzp zXZlHCF9uJ3yXdIBcyI1cIX^xa>Z>~WVHbG%>CbsgBH zP@rR~ZATVhzzcLxkhm&mY~IO+UT6 zZ>89X%wTu-NGvHZ675s|I?eYxGw1nWGwZ2g&coaHvX}%L2YH1vTN#5cL2$d7bF(nJ za)Yn)KIPwL8a=%6nRa1Qk;h}Iu45mz?`*_hlM!}jg~_&N8=a61!IJeK!+ciR;gtx)n9n1RCe}7r{ z4dF>XL%v~Ce*~l0F})JljmUxsAw4_%*&m4{Bn@1DnkRn*?Y7{=$k>195r+-5u|Qaz*JnyLtie`CWoX!CCB7<}ljaWu?uYO)dSB zE6LSjhm7n_Lo}}*yVf!J0;PETkj>V*9MzEoN|Rowp=7Fl5WDf-)W7 z{4PP-Sr=DJc8fWSKgV`vH{FUYZdFppDtSuzMt}DEKLJ@4RJKtSi7^s1P?zriD`)Ck zVSPcha2Y<>=!%#l(Xd>(6gnULg{}^%WsJ~$=;7_2KY&vJD`%mQ^!>m4yV{21@nN!u zHGf%ZqTFcp{AeSRT!T?Xxis-J9I4KDX?h=4wZTp$}JEygyaQCR2v`a&m~V8z$$ z;+qZ6ih*63Pl98$(j-@<{*jBQiC-~On_SU*(4#QMmv>f$%~kYd+Mf6ds9q0^TkKu- zAH9Lc1U01RiQDxD5@r7SNQ6d%%Y@4%fu^&y11`Iv8<|zQOpSuCf0(ozi-KY26g~OB zBoj|X+Nw;aML;u*CnZsHze;r-6JOBoa=ZMsYY)1i(9BeP9ynU4_73J+9MyYN>|Rj& z^}xS**rMnW1}}AccK-W_8h>}}AHZ>eb6>4?@d5E}Njy~i`Co656+cL1)k$ow((iFH z9!-m(6{VC_ud@rPG@IP2ni|NYHrm2Y=;oI517BQcol($Zaw~-KIY$A?Ihx@Fq+MrEW`EQq6n2rQKe)AdZ26Q*Tm-hRbTh9_mLX)x^Ip)Szj;KN<5ue_p}6s zOh$5lRq1IG*v!jZbsl<_U{h@&-kSd{87~B2N@4b_88&a%Z1$1Wy>Xixd4(=g$6a{i<&$qphpi{xB zNZn`;{^ku;v3+*llx0}7hq(-`ry zs#bFfgnq;M`1GMac1{q99GVp!Z?a4F8v`{yq6c1S_ebZzeEtaA8^b5}p3W0C1={Q6 z>wo`@;{`m45hU}Q$0s(X!B?`%1}k24g*no1LYkRep{jUnE}j*BBRW-ALbd9t5hILf zPq9B{W|wjc)CZibFt2p`BBVWmc125Mw=)@JHQih%sCORNrKUM8c~jxs9Q5&}6J5w% zZ`YB^W-1F7+kyRbRPO~f=TYefWjuPOt9EE5e2Z#zO_fJAmu?H3kz_}Lj76Xv?QoB1}|!?v`yM)tN+HmvfOipO^Mq zrTE+F$t-ausJ?Tm;5houVY5Dj%iOabPBO6~aLrfka>hif(io5@zi0YFxyIPFY1>w| zrP?{f<8Zq!in6b(rtRiCELvtGNR(_3PGWFWB$Zfh5Z@rPNhyh&A4Np%56|=;$!cRP z4mC66=AYYL>trnlwcHpn#9@s^dQE0cwlHU7X7J$X{`fUN1~I~v(CEFBd+?C){Yz;( z8Q08-6b6IG*@8LcmE*ZO)-s0k;zJJ16&cT9=B0uE+0P{3PD{NUu@(8YpQcMxJ5m>R zJp@QlFgiQqVeulCV8QGU)-5JBx@T{`QsI!<;bv|m( zcAf3yKgyca=TvBf*U3a|?|N<42F{17CKro!J4lA|-#eGY#Z~;5@_wc0>Y3KDm`U`; zf<-UAVm2pRLjspGiC$MUtD*u)(+1*I1N^#TJR+}0L)~aV&?9?%G$1M`5IIiKV%>;pPiYK=O(sQu*=(@yfq@L7;~ zva)R+nDUO7+A1hFxqa(S|DZ9d->&uU)?I6Te3*A1SbULznXN%LDWdyhuN}spv8jJ_ z8t|4zS2)G}`c1{>S>~5?l(MBi_S%WDQ560=`Qv_l5Q9GVf>vEXEC^*M(dW#Xynhgk zG|ZC=GUxY2BetFR`hEXysmZanxj$%`Rh0ZJZiUx2KaVn~2P4zLWqZnz^K8(lm<$mGPuv%km~l{B515kSyy)y;!uW3`pv98sa<7W$ zer>t+IO$^@$Db}qt_MA@oG$m?ulbV`VRpDQ)T`0-DrR0M&q0x>M zt9(SmyP=0g3`_KL7{N7zV8fREeQ^6>QJXIZMh@zId?^8oFD1_3Dc#0>aOi$=55r0m z))DjwPs`}PPqLNC6RTksmVS)GhZ#+ppb@02 zOU{`YtD;$Z0u-K%#k`xdRo`;zj=u#BWqr2CKs%m)$IzS5#4et~7GSmPBEX`uo`h#} zUf%BN@qC$`bX3edZd&w&il&y8Bs%ErXMYnqHw|4d1+@GphhWIAU8ovWyDp|9! z>Y(_lRnukhtnly>y;OQH&v`?(7(aS#zJKMTEr(7uOEORU0l=Wx??8%4iI z3Rf?j#fM^gxi0nYtUjDs?Um!8Z%19G*@^^6`O`RA4s?c0j?}_CnnVOq@ zZvsM`K+Qx**CoQ7Y(ggIt*2E{W_fZlY zIV^A_cTK1H7dTRRd>HT1B0f%sJjH$<3%q-(>qR4yUm!$nqQbdJ5_e`b&%QYLX9~Fi zo8b`D#5P;ohodcti%>==IL{Ils~p68%RvXT9CWq9SpO^s2h4JSae*1t zqD1q7SAR*n5o&FFeCTP{PnklS!sCH3MWF_)iIR}&;`pQ zx#zfCiA*uWJ|(T(qCY_Mm+3((2VX_MB6=XO0I^HIF|Km{Y!p8!IKnSKbn=5`4D+P% z(~*h_QDQA)+vYQ&0@G_-RkP?@pi$_gL&U;sZ?2vFoxs|(c`={Kz+9h;XQ(46=OnIb z-vkWLZ?j~RcyyLW_p6o$yYGT)Pxb!g^CL*_I?eJDC~2A53O4i$-df`W;csx3JBzUF z?9GvYHETgdLvNDur>M@Qccs(IpGG+63F@}!b2myHocef9zPcFKYwxvcEqFy&CHV5x z@n&-cVg0DDwaYD$mPh#&&?o@9V`7N-rxa#_E;&gfg_EmqzUD419d#JVw}{Xh1l3^# z)&%K^We@7aF-JUiy86Fyi&`{|T8~AX)yM=7Ez2bipKWVMiE2xScZR0RTyvEy=+d9! zbPcaV$!o2pk51Da%aH;IY>a}~&u-upvf6OhJnoO@(4I|pF@9N=Px*;@E#j2N^olC@C+lapy!djet1C)hA z&$nQh6ZqkWhn+nP9IDzo-1mWpdlHB70zB;B9Ik*_s6SL-&woA5E+mDRKt<2#j z%-@jq7itz18LHRP^ec*7l4X9F$C%9Ay%30z2XO;#KkLOGeLm>OOUToDyDci9+5g#X zM~ai5FyFx#i$W&wI`Kfp*E$~w8kX#voznXjL}2n^6VQ__E`Hl}R>%SDoI*-JnV{$h zA7ziKchQG0v0`o4x8vTB*W%qk*q;$G`S4!rzS+(J$-VFF=bDN1YM_7azLC`jvAdXfy zyb+F>2ZRuUozhsRtMePf


CRzt6wNJH~qaL6F5xII<=ZyFgo)qRfy*4k~wTONj3 z(%F9-(Ys;IQ%JY_%#-i+J|s>Qs8IhqTlanL<0xksmTsvZ{n5U(R8Gxfh9-^l^F)2C zDLp00d}0b`@&{z7BzIU`G|m2LcoOI5%B`cjbu(+%`x$p+vY%zgSgoMHJ-jY%Vr^eGzXVIRRI_n90*fs-Vo}QfL3OWS=&s-< z>aTycE(_`1fLS!XLO3Y>A$NGZf5kqwTXQjCa%MQZepEhXApkUwqv;Z9Z;L-=qta3c z`W*fpt38LfH^vxVR<|r8k8oJO=-hL92o%?2+kYkJAM|D!~XgxUA|EL>l zSyNZ&uv?0xmQBU4AG`% z9xyaZ{SD`x9cc2@F9soYSj$&o2cBU8d~reovi@*dLB4f`@X5|vW= z0~1Dgg%y$a^{iD*IzAZ+!EAB(wXYOODI=^vJII^-@U-zY+qPn4s-C`pog@c0B~gBgge2BPzA5EG6jw zR%v@8^UW_gs(jRzKoF?M{D(Oj-`>xQw!g!K#vU4C-W&6?bUj}Ugig;8?NnY(=kJ#A zHT}H3nk&5Q&u}@T^9 zpNEje^edm<94g_EE0k7e*gMH%F&HY835u`RXcwFM^eXvuk9;rws6>Z+NLTtXRq-=E z#Cj=Qb47iW^#Dk3S&_DU7jUuzM?;KU{&h*d{H}q-5I^;fl=shbaTH<-tSL5l6KJV> z;&`M8?(-3vvbX1`3Jl8J%wLVa1}3>OD~-m{ejxa+8`7rSWAGT>nP zvkBVrJ@43<*w9tG3%AuI9^}E;9Pk4pbe>fwH2#my(GF^E(t>1aD9_;JYOC_7=qjZ+ zc_uSmXG*3-Z#Qy1cD3GR_&i-ZSjo*4?$g}Fh!#PPnhA7D-{k5*jBo9t-2$)Ej)IUA z%6TLxI|tgAfD){U8WVa#b_;fXBOWE19N?ZLYB!3wyt}^9s)TRf{A#jvbE0_TN*$Ky zYtfhWe@QZ>58_w&2Sn7|w;<0ewEznE`v}Yl7CSfzd&&R1NVQ0p_yFaZ)^P3Ns}<3* z348s+V01;>d$n8Xso|=x4xv4vNXyvX+$T6dmS!Q{CLKEGOwDt5D5iE005OS7R)Y5a zfgtz~e zpf}FT2QBOfWJrSW9ys^pVgJ(yt#^Os2O|$1Op|$pQSQG_?~`UT&IRv*5`hPvmZsg9 z!blHGKHL#^-^xN5@BbSO?7!*5Y0llduemRP&v~RsfyWGQ+@Rw9_pj*SxCY&)E1yCg z{V5y@5Mvzz#1a_v_cy9KU+*5OLI)pm`Qojo4BY)ItAKA>X&Cy?;Pc+`H@t>nx3ngB zO5{C%44h;wM!Md=EP)6-($9>IgLD_k{lEVL10nz4iv9mPW1l_uiqtL)N4JYz5CY)u zkJbZ^1o-{>#0Wq{3;jrd#Jxngzae(uVr~6?`44h&LHba~;r7$!ue+HSTg%wigHLGp zUgX{)+>sOi`^}H$k%JLfA}!kYHT^Ex^-xLp;_~-touf$~b1&-c!~AG~PUa!#zh_~$ z1o$U|aD_e5&HyuaFyU{~8t)7$zV0W~JB`sm;1ao*9=tKCxn z-LKS7GC=nF@}RT)yXcJkKnQSav~K^YAKo3MoD(Pg4F2|+IXf6XJd0Xj5q$r#59<6) z{w?y`d%`}6L-ZdW3b#J+xZ_xwxaC%4A_ zyS^wR`uz@ifJfZ8e*EOPf6WJ*WJUj$`3cxwz+|%9EK?xeA3ZG??bAy11n=K&0pnhW z`zYcojPC|-_73H4X@@>iBHUB!T(CgT98sSA{rzixnQmkKx6EmGM%lNQ;LSaU_RrADCsVa z8tH&1{-fcpmIbOjWk%7?5et1`Z`cpp>iRAC1we@W0mS`x1G3M;;cPvvHrtoQY&HE6 z1k?+GMA|T)(!3=BUbbnP1G;{8qhe=06$|&yeoBB@zZ+t9ZTE|cHxM75e~#@nI<=kk zej6I4WU{I{vkj;Xp^{I}Q@}$t(|AZ7-Ix9`j>!q`^t@TXsY;%?xSSs`j!7?7GNy;= z<2j-FuUea}_tf%k+fmEQns;EKxzxpBTuLqo$%5mZ|N3_irMWWWu{u!^ayyJksXYh2 z`$-5m9Jn%`mK*Xxxj>=lIR*6JxrTdG0+jkd#{z~a@hg>bOXLzc@Ly_Ot)#fGhVao1 zz(SDDp^%0N#d-_8Oo^*dh4>~fpwIh`9JM|$@N00bG4jNpgj_N?-D#)dP$5y@ohf-B zLqvSENIg?}XJ}W&r*CJAq*w;<<`j4Hh{R=E+Ki`+TUA@3#pL;~L+u228 z4mxa>gA%Q#&z66IXd>%kM^eNce?{fA2f1=7QU5EGgh_(yg-!aXde8e@X~*R`kl5Pn zXEPtRAU;~1tD1Ghx=9tIF3vs}EmF%aTj|9okoe>AXm%j6My$RwA5mgOvhW-G?TPn< zUAj)i>9L+q6GFHK@Ka;r20ZX|J&+jcXlJ*bk8$VzmIrJc7LONUSA_pLhF`|C%h7QC zP?Mn-y&{(Wwk4J^KmY}l~G%zIgWXt{9(Lb1g zL!e@VZ0uMb#Gi*_I&tsZe2cM45`VqabWk23@NXq_h#&|O+v z58J`v7J&t8fs`!{(-T6=#5DSo^?C*qKeQ5Pm*0A;r=@v0$Gf*(VO^RIV-JGfJFOmS!eBRV-+EeQIvjPpbpg%x>l`PS5eYrWe*Fp= zc40Aq&H3WXq?m2T9LXjH#f5{|4Jx6vhzT;F_CA40veIJ!UPpFQs9L&8TI}2oDmZ0! z8e89b0-CF_sUxCT@q;aiR`$u(8A(Qo#VhqhnKJpo(60Al0_q)j8inbz^Sce-dwT zW#uUL(Qiutkv2^^-yf{A)Ip3}YT`L0EL5xW4<_al0316&w%r6TOH`}eFe{8*2dU*y z)E_(`dMffxK(;vYIl&p4MQV1-O5o_X({?Yp|EVd)ivxe#osCi96pJR3CV#b>0Z^?_ zN}=+5mC8+Yqheginy1fyi>>r&Feu1MCNWK+2U}ZM>UfvmM7^!ED|jI(e}-i5FFm{( zqtWZXKkvd$NPhAQS}sc&@@m04;b`ye`C9g%Y@>_F0YQ;kov`BpHpDjz8Omk$q$7@r z=k2D%*Py&-*MZTC1nZX_mAkizMHmU#K}7swnL6A8o<%P5Ij9-u&Kn1mC(st$GN%K*ffe{GrEP!6$SGPkg^Yaw(BfWbTTq+tyg2EYcj3oKUPo{ z0EPM|(#X0^(B}kylueBz5%6D8G=#)IsH;5pI5H##wmT8v|KV9X!I%IY|xnfiX zy|(wL>_9Z>kpWA)z^31+JT}h0JMaA!a%#4q$5;(nEX^Dge^jD5&$K>hU)>jIvp%NE z9fzO89v9Ybk*65X7Qr?T>T$l_O~CF6mPi0mG2NSN}D{3|=spY!>}jeC}SjP}Rx zI-`562_J9t6>26{YqRP74oyz%#(vtJf_EJ;{un`H_5@H~?7o_wmX)TG{Zr!+@5XZn z^#^}TC0BpgJxH6Gz36x$0v2kxG`6S6H3Q_k=xe13jHEbXaHpVz)n|4_PPQVTJyxzr zIi(SjET8+TuZzQQ|8)qz%gh-2_Ler$=rd#7x@s1xG~{4uhUKVwg0Ztp`&mLpbwQ)m zVQFQOwmfZ7KC3b z1Lb{m;8pZ?9{;8Kul-AFNtvwo%y<{@s?73OxV8p#yH>W5e+n8)j5lw9!iujyFz&z= zN{7j5Z!BPs3z5;-;-hdg`E3K4HQUUZK)Z>iR49s^A6TQTJ(+Qa2l|$#<8fz!!qz>Y zAj3US_`-zGm3`x|Pp(|1?=`ka7mKEoePFPK4fB34LuKK^C(Mqwhr>GS7c5`3oWz5A zA`H(LJA^XzV+nuD*&CWoG9M+U%e0IYCZV!F@r{!%Uo^hn7P4M)@}P=#Zy!TjWpo<* zF>Y!Qg7@?bXoRIXHt2}z{8Y8xVqRgb@Mp4+q=#s)%8xd&`WQM$!O7Kn14UJPlLn_P zo4krIhU`sC5MI^Md0|TrsLDzp6UU))dNPutyR*Z~@dthS2~=8kr7F#x*E)EA>7d$u zh!Q9N9R3F3639P)k%e9gpSaVhr;lp53l*^YXc8FOg&{r!2-EEW+?j5W_e-q{!WWx) zr+KZ~=R+$NU6et@)1dHYUt;bt@*-vixp=aW4pagqu2!Z+3K?2I09s+WLVau;g)~O^ z8cWrBBRv_v)zvc+BHDG{__PMCfo5rD^{5fyiUo8>65K%W8a<>YF6ini`s`)1@1!N_ z>THzg^OIAPTy z)R9?c#18UGd&@MN$G<;Ym_YR0K@abYPRr{P3U-6)oIIJT8jV>$yMro|QC`bI=+;I& zABX&{+?-z;^Mh!e5||>pK9*L)?rkz!Bt3j8tZx2J0*5blf91fGov6F#3pgi1Nh2M~ zw$_HzqrMkshU*_Xqp0hEh#q+)FJf+#Xj~pSlyS3F{ZN126He5j6il?0xC{|g&14!H z9%D%IIwGhvQ`h3QXi!+(^PR%{{m}wL>SQ_W8mY2mnr< zL5`*W5%3hRcK9^mdy=Y#R7E-=5}-@RF-b=P7WZD84oRV+KNUz!9E&gc5v45_#rsd! zO4T$iqrIqbV)8$=KmSr#ob%hRCYXiJ9NRI;2Pa|Q^2uU|((~N|smhOxIx7RgB)%#8 zpa*;l!{+z4l3MLiNxAnTX6+n$`xZ`TZ$gTfK+2E`MlT(&DID!k)Bw$E4aV7{uJ!15 zR;aeuE(l+5ft3&1Ft3rS1-!=8NUYS=TE*!8H~q6L$DNr+ku;7>ol(qnF%%|P1clTN zjW^d6R~DBUB|llek41Xp>!ww@cq>DZXJyh|uR|fCOdbjduI`HzJlA92-%c&r5WSzd ze4~qTkf4FMH2PML-STgS;@)BN&9tOaiktLlsw`;tE>{Jls93)og@$}vpnw9oGuhEgf4IMoU@!p*|I6G>GIO%t6Y5KSGJ5q~wd}YOxbash%;&lz zicT}T?VVZ|!P7%ImRu=JiF5B5>~ylP?ADn|4ErI5yUp9_l}WqRnlrkc@vKkP8l1c( z5>GoEOnxo&dTe3Ty_-w5Cf&=E>y%owa`C0Y!-M+k9rqhdwDQ7}p!C#MG;+^9^QJ2a^A~$f7{vC}?uZ;%ftfF_y66pZ z9YoAi+O8$uAD1}+HSla2wm1RbYu%RE$%w1Rn9WN>qL*w6HTxCMu;U)1D8w}c{&Gz_ zdvF4=%K+$oHIVn4LBDEpgKG5!pA|;|(SU%#tiaSMpRkpegKZvrX=aL=JrR1FjhSLW&dO1z(q zz998IS{&aPEu*|kK zaRTx}h%=M9OX+I-xJ7P%VLLyw>&mg*U!VSM1c~GuSbbA^NeVx^Krds}{7_?8k4>zB z;DYTa7%>trsHA&p)NR!&sRJ7P7+yc@3u43$U8tHtKOk$?X@54v>%N?d4nyxeUX#7N z$y%>W2*MjluXnV<{SM~3#Yh1n{o=x=AJjHdspaK-AZBr30h-4!NnACgbuXih zM>4ivN703K1LsF7ehMYM@$;6>zek^aJ3!-&6*cw4HdXfwQbO)mEfV<7rp^{WxQ{c6 zsewq~>A$vbABx#heeKMet0dEB{@m^09CXGI(y$|zCeZKao27mu#25!f9H~mbsvz~& zyjIgbiGDG9F)+pwPHfs`1tib?@9>_y^MN58n4Tr|9WolEQYoFKXeu?Y2o_@8hb})q zQ7AG+T3B0DTc*KvKk?JxiE7E6GsFrk<2{0>joq>IC;{yvoSH-s`^zVao(Iaspm?a) zMd=)z6mq7=oPl0K*;zyIPJdRo)n9eYPwy5kw44C{UVizf+osUTIr zb4&5TxlZABiz-N%={Vn)BjA;vl;`Lwy>5JkbMf7i1?rkQ3~ZvARz3Jajy^9WsA>v0pw$SKyq|hcY^ayEXMVPXhfwRFjJ1!zfj8X{m)TU zoR8A3p>uM>OH@NI&el&0oBXT47@OGkq>MIO+~Ry9A71`vC)2(Qh-`_i^5DG=Hd$vw z+l7~1U-xMhl8dgiNh^#tlnMz%dC2vM+-WdrRT;z!==5JTN~@|BS4GutZAd%Llf7bti7iMMO!cY#U7DctB^2@>DLjg$0j&c@F z(9qrjpHodpHRa#)#!jk@oHE%w8(U7-WrMKSkKbRDPBjXeSHvn@{_ZDkqsxa})auu> zsi;PV)otcM+Dp~wUZO;;XRiVI(?heih2fede%ABrcUEGS?Y?I1M&#>xk4_gCETJZq zRR5Vv%&~~ zKKR>kDLfw$RrNh#{C#PNP+}Cf*BL+&&g{E_GDpaD><7_iml|bX!dc9d?&+M4NobSTLnLT@%??Pq9pv;BB7uhKMD)P^ZQI?Ekmb& zbn$~udZ=uSKP%UquZ!h+xvFl&y~Z2#$Vw+81>)n-cL0v>$-xekV;rir&UbM#ywa(6 zTwt1yZ;9`|?oK%+|lG&2HMD)_fo73h~p@?p*Lb-U-LtE@iy-iah9RtH{SuP`}aw%c^ zQ8>lqzgY1GTg;ovT4vcV+ZgY}0n2xkgtF|OzVAzANuH!1F&Mn{b z`+8TO8Mkq5ZRfigioGoR;XrYp0)dGQP#%K|@rMx4iB`#^Z7Q07jxa|b%n~P+|NYyy zJ`W{|ZBn^!twg0Rx2U(on)VH3S4M$OM8z254pM+jOCJ?v)Y|1fO3cVx|5pV|hiVC9 zD%5j4OYpg9r%{zI%DF0Q%|e$l{C>Bu3#!HyyOp^TFxH3sNHDHtEo#L0Zjw0VyUyhl zY+20R+}E%Vh6$r+)D(YvRZ^@vQy&d!RV%g0oaY)PmFV;q&YON_ka!8T3fbQJs31v& zw!QGz{hIVx`pGeu&^0RZYp{u=K1YmfJ9WOv-TT#)WBUzsJ5{<>3Npn+ggK^4ecEg< z2GEFkjZp`4HC1^U)5j1!U0n}$0*T$&U%i+l#7Gus0nKN{F6 z?_RKMmow6=-HKd01#tKuSeYWQ2M%3XOE$Bvl#hAb0y2Gk1J#C?wpa6*Z5^3^>1DF# zk1SNLyML(C?A{V9k;NmU)U`?`kOAVVAn^@uI_jT}QSa4QGx1Ef#ok+R_2Le`mkGw^ zkxZT~9B$ho`|q+XG9}R}RP`-JVCE-t*0ykFcpXCK5H{EqgQ3u75H^UN^}YPX+Q7tQ zGVj|HISbh>+S$a%Pnq?5A@(UT5K%-_7co{7N45-^jY%Zv{!aaL&yNWo)*>?sa!5Na zM?F0jl`Q{rT8$FB=25P~t#QJ-iiCBjZ5p>>l$}hk*JY0u;pjo10kc_syT7g)NPHjt ze-htyJp0>y?%|ExVVwH!E8Z!J>IOn_Uejz_--vW}uj?Z=a_m(Afd+MdbLhRcQs=f= zDi@9F1RCRx^`6IKoY0QXcBZp|d2;!`rCPm*UbRNaVq#Re*)Vkenx$wrM|vaa2Dt*Y z7KhLTtnPFx#<+DE_9@HKSYBNVd(jxWLUbXe4?M2t&rtArJveO)>Ap@|uh8Qptje>s zYX$wD6#$Zqie#!#^XXcz8S@FL9=3&j^)t}p&nKZ!PM`CA04_e!eycUA3*qJY#!k7} zv@lFq{h`baltUX9m`T10FMN7hpf{|urvI(@7vOJj;(J|55@t8`uO7qn6l<_tS&9~i z@5<&yJm2U^ZWLd^dOq&DkHfXP+Wff)ElxhKS2Uei#(%$NGoDj&3#-q}@!U&q5>fGS zW?uJ+e=r6tF8i>8m7IVE&wHchVL7Q5_yrp1;INPnJvHixLR@C(gBHciKtMq7-ztT! zk|I#+C_hI|xOMzzE_LIkCKOJ^-kG4&yQ?7ixfKs=y2RrJIe+V~lH65vv5Vr7HU@9n zU%3)0x34Xa6;59Qs^b75@e2DuP}8KkM)W*ulJ*F$u*s3ac!~g(XmyW1*W|ij;U^;u z)p?EOkx`z&*-rCWJ5_vVYGTcz{Y?AVASmb<8leByQYg)=s8V|gZ+)PmsSkb8OHGd9 zm~xRsV0y8a>RLG=|Hw?wczWHnSa(C;W?=imI&Ds_e+kYU3S?vE`k!l31^`l(Mm3BV zzc%7`c?D`L1M#O9`Tme$s+($XvLYG(aB_r+Nz3x&_*{9Iy zx{6HXB{3vX4c8+Sz@$KZac1G)NG4A?3^*P@bA^vY|5hN;U2diAP&7K}q&R^cvG!dT zr`>7nlH}&daep~XZbyStX0gfgx)@=td>@WYE1YrR?wb6$&lZHYndJiuI2X5;nYOWOarHMoPB^l z*%IA?@x^6;!7t=Y*e=;^5B+r5`jPm-Jo--R zsbH*k|5D#iz-UDxeqk6~QN5u9v6H#F`j>i1-h?MOE#%jh?p7%ktdj_iUYr1U>{rJCY$EHUkA1)uO=k{*D zVgKP{1+=rzPGZFj7jr~FTbd*R+k<(TKa!qu4D#;z^|Ki~pQkIkBxzrRtZfy>MF>}D z(zrfnW{}gBgOj*1EMHS?lv=1PpSGy&6Hu$^l*BE}w;_3j13DpoJwMq>M^$JWwl3iyC_SSy~)UE|U#z*cbmN)kEd`BccHWhZ&9{3ynh{wN(SWl^|6E77oTnG>^l z!(JiiYG})%exOCVva`kmuS50?1|#>toTK~7>Ijgn&$--ONtGN_PBfP1CT1ZA!^#XJ z$WgF`mIBl&)KjbLrL3rM@*z4NoBE()eX!}O(Bd%T?bCx2Dia)QNiXYkI{t>Vsf(sv7J+yN;5`1+`Z7Mx z*1Qu!-+Fg(@uHCP2FfLAt(T9x3F&b>P^V!7b=CWSc4^f@L1ly9X*iMTX`}=T_Mj!4 z92z6_OTg-<0)eu1f6zWKJ^gx>tP zojRF4XPe?w5i8IUve*@KBL-&KMX5W?<2un;>(i0^1PzgMZoY)=e{vCPVF$5MUf?5` zpuq0|EM%Vj)LN3TkMMPJiLh_{L@VHV2g<8b9v;w5S>HZy?zev)7yJu%mGbrVBV+xB z+C5pG#{aHt&m>``!{fo_l%Tf5j9-mXPmQrHKL{+F0QR#Kmuow}m2>&Y`nw)Av<5~f z`vf)ppSJ3zzJzY%G?_Row3K{DjHUKj6fRK>_M%z`1#)jMh39RCd7cvkoY3qka3~I8lRX_6rFKS}$R|frIqzHqm=eD$h1k6Ckm`~~$ z`3U)-xBw}LnCthUWw&180=ND4WfL7v9j-yvN8D$8LlGazPpA-aSIEP_)POKwbi&7fM*aOyN9QdV+k?edJ}%RdxwpmFaJ<8?MQz#MrXb`dvFNdms1=dXt?OVCoS**R3i`pxryv{inb7itYon1Uh5+BH(@j>|+TL?baSiY-U;eJtF zcwEB#_RV=JsIZuMx39Q)`Pf>Wy(MV68`K!bv)M*E5?wlfnaz<>G8-@Tif1{PS@oH1 zUK=X$=1?uN9#ykDK7Cc}-O>Yv>(`2<&yTYmx_3 zYo9#JEUINpJZfAA!iD8!167IbzVBiT@(S39c+|6xBx2cH^ZlmkN2&LqaFhLZTh4!0 zOd#i7c8MdPgQzyPwB3B0e}rt&&mP1@yQRgQm)V{rEX}i{9R_}12 zUH8%!qs~QQr7CzlX)KP};1QM}XuQK83!l+|G-`Iu_u#>Uo10RnSC{`<#BU}c#ty%m z^wB^+#6CmMMDim^S6mh1N0-VtNtcBSBl&>1_7W5Xa1GQ|KMx}jSA@fmLPyV_6#9jB zh*^Eu$JLHxZPiJ}QP#!boUA%(MmD&ToLJux(eW{SMV;ah&HG2gXgbE;?2yqxo5m9* z$2V$~x|}>0rgrnA1w!PU7IUEr)lNV32AN>7;4YAEzfh|=|AwntCM>i*RPrYCL)g#5 zh~TlY)}eC8_l^|D&i3Z1h{P^`y6FE1%v=@^wv0(q8VzLFTpWLe9PAhF+}|lxv@vS~|=% z3mA{2mpu!JX&7|M5;=GRZfzW>znzmK)@QSuvQa+l7p1=eUAhOv=$B6RFBU@h0O9SH z31(>Y&$TS9{mo3N|GDHbcpb6!1C~j7d?i8ewL9n_XBzy9VdbWAW;cyEwIFm^{XN>AqxF3x ztI39d_cYD|YC0u4?ffi!W;ywD*P?$b#e3Ag22Wf~cC(p!0Uc;>D(L;4Kcj9IcN2sU z$#L+fQrD_QOPuI29Z$|sVut=94aqppL!K4vPH2)HlPLb;jt0hW2!iP|S-KtJaV@4M zK-MCDbzzI;2AVi$h(z$5Jkx*OI$wS>RBi#&^YXj==SN?cTi45+9g^kw_+TK3%RXmL zOwXls^vGo+!V*2i9&a?w?sEd7|2*O0sK(9rpF-46`sy7uG8n%joQ|b$j21twYq0u7 zpk;o^BjdbcykmG_kWXp5?z<4!%@+7M&Q_(=0goqU0hiv~OcG=vMx)zT>ep!mLpkbU zPDh(Bwpl4TUwC2kNw@`8;89v_g3Os!@t{sfX11VjQ&y{Q zQw=`4ei63%Z!H{;%fs25nN&=2i2;T8jVNV!PI(Ji$$DLp8P9MB27?Lf);2@1f0nA{ zJcq^UZR9Z3B=5#=M=&X61l_4PK$}XAMjZ5N!QKXwR{zYnmi*k>nY9Mf>{9uOYIhnl zZ1#uZQqi!l2kQ2^Qs}XldJ-bsZmtjrdCqBF?Jsrzn4W4COYTUJ^p-A}8105y3M_0O z+ZaQXp3^I5r0z*`qY(P>=$3C)7v}8>x_MkWQnDSVE;#!+_q$i(d4B*vgp}Az=}pkj zY&f{qVxB~yDeGfMe>NyK|Me~AZ(*Zp z%GqQ3*`3Wx7h=E>()E_g7blV;_~l|GAw#jK>jk_Jjv2}{;ch@X5CgvwueG_fVsZDX z;375ULHtb7PoP)hng`;wq^|YFF|Ce;D#v@Jee0VaX|9HIsf+>|b6lOz!+`$K6jWV6 zz5~Kb=tUUtys)2_L@>4Lo}y4nfq?|EUSNDD`Q_P0{D)b#Uo;r>^j@w-V)|%)bLuvNI6UR>m%{79*)FF)NW-CGAsb6vF_%FC604z z^E&5MexWZ1F%19mpO+a|sZefkZ~RTsPQhv5dtPHrD$Jy8_H5@fX!cN@BeAkGwR!8% zyQl9QJ=o~HV$4SOHTx3vD7nN#kaUvYScof*%m6hs>=_^RYHt=tjwX>1Aj7lofS4Gz z<@f%nmCNdogJRxmCJyFrWB?oZ^P`MG;(W)$Xe>dG)CS+5_KCTdg`}aoURmRkt7APi zoAkfCj)M8)#U*b7Gh!p#wUIn(97+fCjQw*1A&bs0_|qp=#L&4G zx?TE=>ABND-Zg4Ed#}q|Jx4^Z!jk%&Z{vOrTAe~0Pk;fl7Ud78j>kBo6vLRS$oarS zSFVg2Do=K$!oGJ?%P6DmPLA8la0^A}#5X9+j(i~!b0JQWCz%55R zJv0Mv2X43MVc@C-AiRC?0#27>1EPZ988;ndA0=HOegztFKaInNa)p|Z2;B9Xmt);} zwFtpS!hW%<5jf;UI^S?4`Qi;8fE9mOHY*}PZV^Q!!dBK9WeNERf#2X6x5r+G_+Fyt zz_4)Mf??@aViF*k?PYh6;N^oKJF-YrF2G-b9!7Eml^RmX0b2^7ev>mwd)^^Gsq7Uh zeC_T}@hPKmhT#`;+23G1?Y+ZRQTlQPN9&ij{~OrX0Vp$ z2cFA+0%|AV%~K~t)RDs$M6y#bi|M~!>wO?ulK6h<$Olki=YwISb}jq`BZXv3@TJu1k8~UI$U^mT*+3*j~5N_?j|UHACY?^$@5cDj6zxm z?YT{*F&=FE-HoNI&2o*({_p;{i;e!Uj+i!O*OtK=$s`aPL!Eu7J=D=S-t_^=CHRD3 z<2Z5C5Fr=MA{Et|;Sf4J@!ccdiU&DY5^VM;74xC~_CX(zqy+(6Y_JM~+(ej=t6&$g zY>K7CZ`P^EG+#fd5dj&O84K;^z1l^a;2$+cU2Zw>cKgWHxyB(XdG-I%qY$uQYvHdq zuU97mi;uqeNbw4b{}GABZ%BPBhl3pLC0t7M7E{rGLHlDOf*>Lex?b)vy_0qvysO|K zcu85%z|p@R1P+3-fWmuM3HhGrNO{7UUrX|eNXh~gDFPVN^ZTCJeBl5-uaf z5T8xsb+q|<$-h=cCAk`e04m~3eUcU=Kz^xuM1vFrmyZ?r1jrfjy^R1@JpU#5@G218 zu*X|KzQGOjAtWcFq>GI0>i<&dBmVjyg4egNx7SU?_WJQ%Rv7t7ej%~`m@zEES3J=q z1Yv>VL3X#u)rSVtz;pct{x_Zm61z*0vL)bf{k}KEHpmvq~yl` zKIMi!9sRy*nW-51NF4VV!2tly!^U;>1kZshY90jK;e?F;LZ;^yHvWwiYiBrhIDA-J zhK5cj;?uMO6Pomv%C$4$v?fZalp^Ea?ltw&biov03}tyT!LWBP$Q?vIg(i`juFn@gW3-=HqO7OOg4RVJ$R&fu52zDU^{isWxyw+$G29x1&8u zICH!R$j5v+Oq!)#8TQ*~X|y)t$T@-YS$q&Pyb4(a1k9T*s;m-0}KyP6Rj>1F93cZ(*G# zK*|1WU8Q2(S6Q=R(STV+Pwx0hhu)IxPDjjz-ICdQ1Z&*uF33(1^rM{yo7X$SKRXYx za0-nA@#=sR;1AtA#e5Nh<~ZJxq|%3dxa+jql*wNL1uHWqif!Vk`O*B&h~mXP=r0e zx$wyw(NAj$7p#s~-D$9z#kF**#V>yvcoDtx>9Z2kysu}fR02Smb_PZWYCE2*M)O zG`;U(o;0&WUFA6lfF=gFhv&}-fM861oba}*4 zfzO_yyFxoILd?kbq?yva8!k2ap)3BZK3KGt@Tr<_u2!{SP+s z#|3e60n`@Dg4x#%x@msml~z||b`oE|^aX=n5pAY7t*iS8!-JoPoY%JMNIY|NJ9c}P zH`9}iKN5K9_#yNlXCRI+YeI4l4^c*+VduT!_2qOXZ}i9TtTeJHaY!$b^?W zaF>pFW2hvW!tL;V&o97H2;6twO0k-(_|9(NBssQ2z^bJ(HmJHmjThvV_S1_Hmb3PK zuDMhPHW?CjTKy`BwGwKf?)~@rFmt<_#Zt3gNf&q*H!$#VkT8&B1fbOGq<`PSqQ$es_-YaN)I`JuJEZ=jEUFR!9EwGaNDA=GjvI86ftC?@(p^e*$Sfg6_`QQiZo6n za8)!#%U$>|M{DOUK>hq>g7n>@Sj)HFx*sC-arM2)NLBJAvr=7|*4aV3F#-2BfQzK- zx9EbxW}V!&M7o>o!_8T|DC4zgwEDJ|TYj1>vH4Gk57xLm2eJd@vUJ+kej+vyN~X?O zjUFo-oqslQSAMnm8p_UhRSK&n+Pq(&zjJwM3a)%aQ##1;5=txWc)5$>bI=pi=)80^ zN+iYgTG_h(p}EyfoK|q zG-ADqViJp%#~<%8I`3ys$E#rexJ$>6$mK2>iChR2hA-%k-#7)w4e8FldD4i565iNE z_IM~ja~2Ubc%oVNwv$kJ9A@TQjy*ImfqLJ0Y562uye?97dmC=^D1qDR+eBvXSQc>< zt8=F3R=8AKV{FsvWRa?~&AB!oW=a0gQ<9G(Re=@u52=!NVmlrVu;TCiVk)`K)T|w; znaCcg(|*LF35m0w{lnt_>t4{ei18O*BUmOc$L}%Qms_faun^lkRC{hA_H@#;U3#SY z1jDIT%3%u54@!Lh5NGti6i2~vjV=`iD7TWti2J{2FLyg3Y+(aU^IC#qQG15LtUcDLhyhz9PK z2A;~q@tIcHzLMA>V%}jC+WRMSc*NVN{pzR09_5Uw5C39om}${mFL{wmlM`pAC6{Zd zN505wlMt@bwu&v66rf|D+*PucdP`(9CkBrNa>r%a=R(NH9b!?WSI)__Qzm*~Z=1lD zLlB_tVv($8V_mENG?+*=OS3YQO{te7^k_>miN7d!nf#yVFTvwKXPk>O zm6*D1;BOc2P@eKn+fnJ(o{kuq-BpbrJ=c2~Ho#q~i$#$b8JW|U{@%=KuRketEB}5{ zI~qnr??PqG*CtTZdoD^&u7n$s0%-ZMNr|RkY+wx-uUD8#1 zIc`kHIgI|Lo@>j|i7J29rB>_$SOpk^peY$(GLZhJi$7w#EaKebOK|KPMtO)B`Th62 zD+)5*Sa6FTlhG8Nw%Q)76U^k&Nw?lZYt}sCPjw1EwvOCJ#E3rFjBE_J=pSu8C9XUp z=Sochy_L7e0o%zn{45)`qnp%-Tr7Q47-SV%|o4>X!x^nm6foDRxIJF3BCb(=hojR1; zKvM%OUs`kKCav8|wgT9x?Y+jk6g?hkFJ*~tN&a#BRWca(yB<0;S8s*)S?4sGP0$6# zCLdd>052ki@KS(iEHH*x;$?5Gx$0G|9rpK-a0ng-a2lq_{=x9x6> zHf==T{7C7%OZu_Xf)%eL?p6HuIa8}Rzg?H7O#|HQ)ZYq-r6sq9wWjw}kKMr}`)T;J zO4Jx!g_0vN{fIQpx;I_(4qYq{{~6WGxZceYv99+vdpYXp78MJM_?!=WJ9dT&pZ*I> zGnihAvi&H%9?`cQ&1puZg_60#o<23eSfw8sWBbavh>K8Zul9BWf!@7b_(sv*l0)ek zeJ`koIy?1YyZ5Ha&}W&i(@VzA(J5xJ7Ilc-B&&A9N(|=uco*BG%Xv5beD~P2NqE-p zyWyzTQ0E%-ag>T_v-ajzQrohpGyjE4RBL#io=BMInl}a!+S_umLfCKZb=U3`Rz-=hjOmq)#pi;mo9=-LTjoH^QUY}K!#FQ54VmYp%L^ut@@EinaLhJ^~Pzx@}z9C z!${P+6DGTm?4igi^fLD!hu+}VEVC?h6jJy4FiQAriUYGjw z-{y;)NxJjhATHQ5LgG!V%UbR4HT?N`r^nuM@yS{&_N}a(d8EB`TVh)A7uv2ZX>QV8 zcGGf^{KPiHJGK(?koQwXYI|SczM~k1#mdYv#x4~x9mK__L~*a2H>pOyo8w(7IVG`XJfq_+fR_B z=a^ybs^N#WM&{00Q7;!>0E=zdQgv$!gYK-;6krB9Q%Z!?pOX3$QSmuU*u=N~X$Um# zsCnroPf;&oX9GI6>cX0^@|xx-+}0Mm7q7p4D!du7A8oc=G6k(`waA7owPDn)#^LQpbucq+1`oxF> z)x%S|4okh(D5)5S?)0AT9c7l+1qT;IL7rCdnbtG+q%HAmqrPl>^@Vm)BNoZWXOC9b zzT8~!c{q{%o?ZG!HU0pK_gi2vl4|5E7v+@FM2z84oAta>@e9bUMT+7)KRf9QZ9|& z#*ABRJUVV#H-rmM8=h&#Bs7+Zhe#Dqn3&8ru*Rv(s#NJ0FVT~KzHQGX`T~oRErd_4 zO5%3U=6lz4$)#`8Lz77v%n-xY(7J`_M`+=)XnHI|m_&>pw@?i8!ZN9sv(CS>N~2Fwcv1j~AI28>vXwYAmO5Ivq9^1lNiCwiqfx!Q zOPO^a=hbK}&GC;qy{c3~t5J7$a%HE5g_Cr%%E|z`u2{b4d!!0En*-GgRBQ&`+_L9% zjbw0**yG9>J-4k|BMWA;L;ZlR3Su%AF0O?reVVRUdjVW~D>r$T-MrY8nv#Ow`O_Fc zT{bUUZVdwkPx?AksLG|uyUNcq*f=GPW;DLVjJicE&1qdNzL3*bJAF{dzc(x|I&`OE zRcAW9J##$sM_1BTh32PctL#rI!^ZHoogSaW9^5tN>bJgC-X8&hxK4mK2%KC_1F}-U zs=(0AwUt#=`-wcCZ20DNZQ1Pd9*GT`le!nY%Ah&Gfg<91(iLE~MOJ40oooD5z(^B2 z*Cfmu{-pPyB^`b%U_n84X-ZM0@E&|Puty3o|5JYtRc_oU{@AaipEJ*dHUk7`<;n~4 z0xVY+z83IVtts`$!UH#Y;!UE?MXXw!hHzk|W}3pm#0kFJkIR3l&owoBln8j{e|P&e zzb2wH1zEM8Dhqi`yhERHaklXQwChgZxTi?3L8%+eQrN;mf3hg;tap z`&p@|FkH1%+HQHIX@|4ps%tmEfM57NpO~%P)B48}i3=_Xg?#qN@$f=l#*U9+=+VyO zZ?R4p*8R8PkfI4%z7AhPAAA*Cbhwq(R8n*BLrFRJN zg~{auOj^CtJeh>hzhUVNHzLJy$TS6t7Q5 zEov2T^>DZ*V4ONl@PzX;@tN{YE@)}o7S&lX{lCcz;Q;(c@sk{FGbFEAu`i?|=}Q;d zlFs0|rVv~u^RpBClqX89{tayrdEI4O9F2ciNGLwAUdE@F8fBQvoFm6H$~DZz(%#tV z>-MMPW2{y+=j*Dgp$m`WAO}1b0<=yJ=GuCauHRZCQ>Skeu*x-M`;P)##GwYGLT*V^ zg6*G^db^bu(7+$zQy{0efdZT+0jrKbVT*b`c&&GCao{yf$vm6y3eaNF5)YgoTtob!YgVIv`#Xej$m;R7VS&&Ip>DDHzIv5D z!2^!@I-A0+g;L6=+wH-BEQ(^PMkl0Rl=P-^1)htbx4>4>;mh>?%YQgpn5d6m4=AsX zqcg`2AP%^DaC0JhTlsi}cnwiNlk?64D1h28kvqQzJzw+3zZ6x`r z(1QFM>jZ013Q>kTIK_)UUiP(ipZ--PsvxY-I22zDyAfeW!gDI*G!v-Y^uCxh(05Q5 z9n5--HEUZiYu!-ChM089^hWvT70q;6zG$|N!t@km+;Cmot+L;#3|ozhH)4+qaupnr z;@UG%uLL+0*2>-C5>14_3D+?7gR2&h*Bj(KJA&M5o&iIPO>^Aw$*uC}(kB%yJ`aMJ znp8Q-E0x$J0X^U-PyJP#x``$4MKXIii!VK4CKg9(*n=#TWQkF$LrH!<)X zg4*KJP$~W5c9(L~AjYb4FL79NOf6Aj)sG2up-89wsI-og8#u#<&C{#tJqjOyv*zEo zt0}*6Gj%@t1Td>;E}8HvCDn*nL1>xdkXuBy(>{MlQ|!Qdv_#Ld-G+)Dwm3CARVIK+ZK^K zidjq0`1A8yqJKN%P82Q5u5)rNL>c`(fc@`CzEG>NO?~~dq;y8$#LSf;s2^7YMNc!__b2Rib{ zx2JJo{XWlD=Zk3xYr z#Z0!XlO_C#18S)iA0ms)2Ds0IVroV|snt%tLCw$&B~=bOg=JB&)@t}JS1ASh6F${p zX>187C2+}rMaMDv0?rkAziD?X6AViq>LC)%JC$U}q77?ZD*V~qplu$Sr=lFVJhHa$ z=7%wcU+BfIFUIO+zfyr!_hk05ro~L5%Mr~FWioMf8o_tZRDfrzio0i8 zptW4>Z`X03gr0hTyN02g;T=7=6XN>okS*5O{q>?&%vO2t@kR?zaLIvb^bj95eje1f zfH`bfJTcc+EiH80dEWaSnqKLkOs6`Sv7M2Qjdx}N-*+UXh%#RyQjCfcP)uAN%rfp& zXzxlIE_g+q+Ba;@jIUm1>1N}mS95-_$fHt4TpYtc4Z_%Ei&Y&_S5d)$Q|>QG_Gc+! z!w^0jdQFRO`Klx$+?0q->?j0YOi^%U!6e{lX=qK~hDMRMg-ceWa;}MvSSIRupW|`~u5ht;Ck;ARZJ*Zflu%+C+j> z?DH7IPPIh^7=Ha>R)KtqY1EYYiOl?hqRMx)ojz>ScO7^>g$vB7TX=?lexQ!$sd7dX z7`oP*y528Fk?!PZd4ykg2wa^-y#t)qd-|QSH952;6BF-^f-71ySLNJUVkZ$x!Tr+O zWT3YuHzyHKq&UD@z-_S?FGBzu*W%}q8TOIxGUcjOXOS@n&x;ai=1W{n<-Nl-__EtZ zEMWdY#+-AYaIYiZ#`+iHD&ULr?L~T*kC5^BaYrNVIw+shiBM)?y!Q7xycYc5x(vsr zbt6->|ns+`!ClP#_yDDRcn9H=M#R&hcAUfRT^E3UgdA_M7DTsFG z)To6Y?;TRk1`jlh`90&$V*00GvRj`guC9frO(+gzUBnNn?}5^Din->f|3e0M+Pc|W zuYWw`G(DU1y<-!1(aJ`rPj;9vd+UQqPGEjcuT636{6U#NY7j7zR!IM} zE}pZ~hMuHc0WAYE(JHZ~%9Hbm6O`LJqF;s^t5D z_3lv+xpTVaoh~ssPKe?G2J7q(B5lWT;3h!%jLFy{C*Wo)>eT{>Y+LKj=Q86Ek%MRk(QAg*5Y9b=VuBL?*&&m6 z5A8`0XnXk*ts&C$OX$GX*&MVFPkOoX{ z(ePLGr)%{%el?XSH6I7Qsjun_&}Gkga4^I4gJM&(Sj|}^f#a`U>Yt8;N87QX)(Q(S zMW7t^`go`70%4e!Mx^cXu6={K7r1$U`Es9}6{|_>UhLgrtO68iR6;I|GO|*T_g?nH zK=q=(hx_zM(|KkJ2T^eSPu!5sHDc$d_hd_x6V=amtn-XM-cE0MX_uij9=+R~z||ql zK3e1!F<~aJjo%_@)7(iJO{?DDRm9_|#C`gq2VY|A-)Hz@pYZP1m!~vX=gou7I1MNM zJb`C^;=O@5jjSZJe$as7vapH+;zwfz{6KU;262&5+d#u4;}+{svke#iQiU}K4vWbO z1GE(Jo%D1c-X?}glJ5(K@Q!%EhK3bJAEc4E2w;Zrq)-kz{D1DaEiuspCuut z)(4l)U$z#jk~p9+e(8r^AnL6#sFrcg;)Eu0Es}$1fw^{c! zai-C=AUhdZY0y**t|cVqGQS;n?4V_LB$ z8Q|N1gRx+Z@!UP{zC%I80$!RGIE-k?L6v5zUmMat=z2iMK|}!A-eaSz8Nc!V`}(bom4MxDzGSm=sfIpaqsO-ifoeV1j*dh- zUHK=MRULTZo#0-h`(T?ld@Ua{NtRYT-|F1cK=%=|PYV%uTXKv}oE{qev) zPq(ANDSO7Dk#+Ggaql!Fo~O`}x_&SjFxB7Eib%2DmV@C6-;S;vTiWC3ySx$`7%o5* zW_*^V87eb^4(EBB_rAeLa=A>E2)nE?h;nR;Spqrv$&jdCq@OWjg_{3p4Ev(Y=(d`_ z3zS6%wJ@d{!&nj&Xyr3Pk}{*67hj0OC&~s487yHIs_%{8{83it6*$BVEkaZ*x`^(4EvR)l+f3WB@#Q8V5$PYy}6b|c0>R(n{7Blszp$&>q>OANsO5> zRfQRE#u`lxZvl!6J7HcY{S%|Oa2ALuS=GCPX~^gC`xEr&*B}NYW?LmCT5KHRWLoEP z(W~0nKWjGXK$4|%;KxK-kIXthA`sJNggze*d<6jBj&W2$H66P1CG=&)LgAS}FU?r; zlcb?H0yh>^9c^h?rH9g#jr2n8?n^~-w`D7)1?4QR-7aq2%GaBetG=Dn{!7eyY-Q)Z zJ;%3B!an5d{nJ9KGf%F>OMcphCWas|`cI5c_Q(5b^emK(Ly%^mzav1|c|C^1rQYvT z?2N*I-ym(eR-s@1;UwLX_bYTSNic#x=*9X!q+iyNY}a=#YYajXtU#fKcK^UVuZv?y z>S-Dy`xSr`_Y&ti!@%>tHJouNGo@K3Hi^Tivs@zJ4f3e0L7=`TW2f+>QGfof7wkFO zAd2lP*%nT^1(nhyX`apW(IAW*U+PrP7AcvcaPSj0Z!B)t_^yNRb+xt~?ylFUO3 z5b*1+MMw?y-;79jzrwaJ5^Q+`1!-@Rj`Hn4D0>8UHZoUG7&&?MpOC+MpBGOFxo~`e zSo(d(U2LJZ$iI;kbz{}=`2pVu5)l6nL7D`%!i>MX5@%HX+uj*3W9ZKw_{q?hN^QMD^v0l>o;1>%+LgwruyEVdQzlX!i2hy}BK(p6 zY?MysA`G{^ox3J(HSVDol|N;bb-QdHd3>P8p0wvs<4%f0PD0P?OQH)I`2vR86`kTS z0su{4sx2?|i+sQjmvPg=j9piR|S&D+f78Bf3BbLb)@lSikfM4shp=RK}gOJXnp zTtLugpNK5cO;ed=$>o6ogHf;L{v2c22RAL4WsUtB5%2!m-Dozwn+VdTjGcPTVB}v? z5a>W0uOEt2y6=D8(|4F~DPIH)`(b0tVQKL1w5R6XfpuvLz;Cbx4^O2+ zqC6Dh*)>OBbSzFOG8$ia2o)Lc{7=AG#2Cc7h{MnWo%-undk(>`KccTc&sXGTr_x2e z>TG{^gn4v)J$K8~o{| zrGSHO&u|5r`mXdqZ#3L#)uQJm*PK<2xnt|`AD(b53SPkomNS6AIV?Fi1&-7it)uLB z_YuR!9zezYYyO23T@h&r0oZX!KF#Q-K!&UL`VoLY$)@p1qs}mX_mS(N@qSa{ezwQ2 zS2L5hl;}gjE|ShCBmV6XTL9>bB6y1Lg-#-;AR`BN^vq|0Y+%&%4p2c7>d)l*S;(*T zY(~|4C=1Roy8-0i=@T4d*$rA5rpIesf$u4cQL1rI6*U1%tTq{cD2+EGg?71h7{HQ! zfa^7HPT{$VJQxI2&Iy;o0^b97EQkQ$=l-<3g9r1$S`jkt<$E%{mA+zY+>6eI(dd4D z#1E#~`2hYG7KPEFXJ5wH^;Aan7d{NyX;OYTg^-kAF4ksL%DnJB)&|JZCyJS@-*qk{ z`+F%W>NP`^&jNE-*xlc^k)i*=kT25j;06i++JLRKJoDxm-|pNy>iZX4%~DR~Wul66 z?EZv|Vn9xULs$VkV?bT1B4{cl z+2!naUd}$pojy=mz9i(VUd?`DAoum``(4gHuRYofvI}hD$9(3Ohp`}&6bl#An*i?C z=5fn?#cv-@)qXruBZcV4T4r77zFv~y)}wo&?4N(?oB};$g>z4f$n({IkQ)%8bIZ1>=ao1vc+>m0lPu_0iIBwnu&A?VE`4*&$kxr-il?KT7e+u(Rq z@wi8S)op3K#QjRYTDu2Fh`j4I-3wsenRg$jH&yxr(8S38CIwEUDE7tv=tk%xWBCxR zPTe{M$)DmgQGXKw_n=Xci0t(}cm%6~s0w>)k=A0xZg0`R3n1VUZv#ObRgvkBOWh=E8m z{yzt@&=n(AZc|NFu**d!y!1EkukU+Ge@h@6LzK1?!Jd_|CfHp2XjeyuGMLtC0F_472>?fTYXt%@j8&yX`bXA+6<_x_o!REBd}60T@tCZ z-#CyUTBTB~<2D^&E;8(g$ThC=1Q?%sk<|I`+--|wd)4$&h30gjLt8=*etfTN;EkCY zSBgVSAKnDu%%@=xv8X1aLP`|07$639*f6@g2xclS24Z&nyh%ooS55@K65*QEJA2e`eD>17*2^R1SMSmAR_P0PxS^Grk^it{{PNW~m*V?w0!CqbjyX)!Uh{+0eBw_3%=9Lr#m@dPa<)iD@o zC$DBm8`bUL6k0k1mayUJl*FI)%?|5hiNLfa7*y4CkMkRz5F-fkk)*nqDPmh+NjCP^ zM(g8%i(Bu1Dq1T3)#gOiGcc#Uk21w3@7Q(}$w44a0*K_)h*@(dR4W$%2L)2k-ZJ;; zX<_5x3*l+m%&2Ku6=*p{o-d557@qBG?fqZ8?OCo7;^r2TeR54v8m__Tjg+6Fkc1EIBz}X%2DHwOc zfkhjVO|T2UIcwB;2uc#uM*8aaiybciHEuKsoM)s+4gykXyqg6eY>>9D;n z*Csc*KI-)-E{R_2PSgPCfBCcMqJXSAHN=#TT)qNjNKZWZv-^H!6*9dJ0`X0U<)_r9+0w?s+u|0mPxF;ZXe*bPO-snqIVkc51mw(0BK8?NMnU z)1ii9A30X!yS)L)!A+S^%Wj6Nr~U$&YSE24>Z>Q9UU4u{NUm@DKM+1sxs_UfuCY(` zAs|)d-oLku^d!X2MaHwKk%n}l%clq=fjY@2dI|0i*C(I~NLAH7{lmW65s0o9;A-FT zfKgqI0wEA^b5N6Y_PSj+B?joNWX@=I$Vo$2MpgI`^{Rf ztX^n^4+Pv;#|8xRv?B|vEqGt zwGgPt?*2CaqL=qW`v2=~VuAsc30tn8UPF)phB?;lUhs4$L zM~}GMgtKVZkiH-%NvB!*%O|0ndydOMe2xqYIN2u(o^~h;E`=Ew^U%!1SQpP4Gp=CxhMg^HR4!l62c(SPXxKO zPpq0};?N+_LRvsX3POIg1jtML-!!ie5dX#T9M26#G)8J%u^s^79FR;? z)r$m0ORfx9?8gu}yKQgLNJMQ@ACG>pXc^4B10!Mu5H2d`ODG8_cU{Ng|+st zx?MCtZvFeTt_Yw&Q|>_MD^oTDsG^W>Xyelb?Hhy2@ zo**IuQ8}SM`Z*4x$o#;FJlJ#*Hv^GSohq}HCr+~=eTE4oW|N-*k=}?xot#brKT(eM zIBGhJGd7sZ{1{eYNBQBlYyJVA(?5?3j^JB7=f^AVie(?rc!^IK>A)2XHc%nOg{THV znaNGS^{AGmy5np+g<~!U3`Znr7_$>c~>+(9uAC}fS4Exti zebOa^U3K}60L%so7*C8BDop7q8}ntr`xj|H(M23S0@If%-In~DHe^6fLgd{fY@Sv? zQu2dByL{gHc(F4jz$7)a$xz@wmH(P5Nx_1(1ZuV2138t8?NNn_zXT|r?7_1HBgaZy ziR>1Knql;4%?M?myiF!rt#TLd+xJOlXo)Wz*U6MJM#Mxo4%oH#WznShCXgVrF6m>S zm3AM;`Qh4dq4ptxb2&D_f8{PS&viwpHzshN&_+MAN(Ci{pXsg18Es1GLV#{1{Gep5 zgzvDzn3Bk_^dj3czt}zV9i!;kvHezNZL5NH$*8*)$<*cff{Vz(D3q1fv>brX#Z)Ka z_RuLI{1I_rh*hEDwwgZ%QZgjSb@2R>sUq^sd^DAW6PlL0I5X*f5hqX5`}Xv*{Nrd) zdscHL0fhtfad}C(KV-{PfqVoPBcS1n%=3ny|NhjkwKXwCoqLcC+!4B@|MI#VKFoUr zUSAQdb9?S>&)3(lgvTaxwbP=Dqp@Wi63u}DV3GtuTX$%NjW^eSSioue4F3}r@L0?1 z^tJB>!)yFmItYfWe2*@H!HeCVB*xwP9iDbQ`*aAMQRUe}7!Ky;Qn-nzWmzfpuiJ6u zx?r!vz+^9FD>fpEE*_0=!#xo$JYVQ{|uk3^61W%xkQaobU* zEB0d2_tpDR%eYLrQZ{P&*U<7Wlh-ezR5d2dWCtQ7U48epLQu%K3CQL?QwPt^1uls3 z0S}d~u}XIBpyYe8>Mx$+*$@`G_ZqG-0>Oq=n2lb(I{Q!&^r79_3xS_gJTY9^+6>Ey zLeC!*yB1K}pUB*U*Pb88M7l>USeuVj(Kf*;2vu8-f%zRgGJ-d2QvoW`$veWb5_DNq zT0~q@o0aG*2Q^Lkfa_0^av)m|TnA~`;?Y9E!vhii?#+Gr3PqpM{kK>UePiX>&t)m zsorngiYGn_(g!vz&pzIn(_^$OC~L#$-LfAi);!H@@Lt+qRkMJg^F1Qm%971s0lng= z`K$WT_UfnwYL4nd+A;GB@4tM-l9gxl7DX!0_|6)F6{+0K5sd4tPwD_Zvpi7zbZe}` z2j(+B-Q@2}^GJs8D{zs@+MPo9n_f1wHOvZ}$CY-b(9{w@iM}6HJ)(a`Xzvdxxhqg_ z4YHpFE_u#+=9t{RB&tdRdqdgkrQbvk0}l^+9hz&?L<9Hk{!NovAn3dU4kWU(C*>rD|}@lQwQF2{&U2Cdw1Ft!ttQuuv z1Wa6;-&>=U8P>kj@htH}lbbxZ04)nOLA~@mDZ)RLOAFVL@b;|tJ%vT!4^B^c@E=x>?iNCe{om2o)7|Ah|q6zGQbaVb{E$m$J1(R{@c3Zmfj9mBq6li)E+%{D~BoLWFHq&1T5v!yzbxe}V5;ntAeQWfE5 zwc{3UigcVrmHzzwR^@D3E%w4uvkNP=ORbi#*r>Sm=DcRjL1tOT%|X?m0zJ-YGiw6_ z=r1DLh6fMJjdw7CL2p5?E;i*=PECgB-XqDAG;^&>B%Dl513ZOrxf+*pRPH5uMsu$F z-2BYjh7Z15p{yyr=aMDckD!g{yCW3;)^Q^CZYX_M0FAM?#GW7JU&sG3fzq6C8m^PDA13o^l`zCcD@q{vreW9e-eEd;5 z!17}jMVt{akX>Fxv9(cWi`_x{q0xOSpQn7ib$iZNKmOQHJF>M&{^-3!XZF_gahp{R zut|?wfkiknHZ6|7FspQaN~~TVIihiLGX+nW~jGeWCXhFNEmzW5Ys=J&1t!?YKowylGxZPWN8E=}1z(z%tfGDwg~{L%TD z)Z8`cjzOLPwla_*ty$?#BWmOvE3iiw;B%ih#PZq>b?6vK z^f;^-B`BnIQu?QVOQ}fp!2j*AIwZ8lyL-$PFZw)z(>Ww^oi_9w8_aG=Li|pt+ph|sXG(bv96c8i?6zT3%Y3c3| zq}?>q2#SETq;z*kH%M(7q#FdJyBp@*Z1~T8KQptQHEY(Id0xy~zPZ)~?EO3Icbv!Z zIlf;;VwNg_TIHgOwom&}RIl;ocBm(<6ZsL&aN6nmh={)R^V7GPT&W?>1Et?|lzHzc zyGw^A+|?mxu~^T-sY@1?Z`Wz^M?SC#$*httsE+Lg?Q7j!-_ru*_X?DXnGaUSUOQxY z?gTgRWw~(m#{TB@JO;`Z85GcDtD2`~zrP1$M2Uqi%FRM{Aw2W1Pt9f{$d(p5RWcg^SAFh zh{$oEu*yJs@l?(NgfF7&N7RHOY2ggRQSKV44uScx0ouX(LSf3;QOlo;BLQ8vp~n(e z?N0BntFJp!q;(sHuMW0$Uz<6tbyL6p^@EM%vN0ucXWa?3)D4r~qHKaSuy7pKjoI=i zy?dMxt&@l;Ny4g++tlP}X{~wip|ZR5D-ywO$%ozo4WpaMCYQtyPxiF9BDXUVHWPcG zOGTFm^B5Z%K1#Ed^kBy)>osiz{E%Pkfy9q&rwGTN;GYE8hklySNU7k*NORT91YIqz z)S~z{#JhJRr`Swu-)00TP0thPnU`O2%D_+X;OhjxoxRi4f{XIgy(2@G#jOJ)4z3#6 zB;ZUm{>t7-O1#iuht-L`m8Ef}r~dAJ(Y_9)_yQ-`j+h2qU?XK^$;fJ^S|CM6+#G`p zXwzH{rH^U_0yVEdYn`?ZX%@TU_`0rR^Ap_cseh;bggml?EUIdSh2B}93y18xK~`Wz zM43X|u4!+6P(YsrmA^~|ADQ*53%pzi@^uC)4q5ht4vT3*k@|epTm*xoUkd zvr&)1IIY?A>SOFxWc{J(e6=%Qwx7K_=ZKv^<_;s6jM6=OA~DJ6H80oXO_ zM}d+`X!FWqWx$NZ!p{T;c!Qb%tU(Pr!YC{}Zpuc`_f)^3PPXjNM!M+W0{1qnWNRJY z>qz%HY9YvBj*0)_qLk}vTD9GpW?lg%3wcIuxU`ey5%8)lBi_gG*ZBgORY)!T3T+9_Z29Fce;Jo96FB!=hv(-N{=TB;g;jH8C0Xk_>l z>2tu_%eJsf7GCL^CCQ78JjeI$cOQ0|1-`6c2>b+H#-+;bAT#+~Lj@`Wu^CYb!o?q) zJ>}CRgeL;LRMGHyEP5=u4%0<@8|DxbCwBjn{#}O^N1D_bFMr7xWozeo>h1#d5g6O! zavVl~kj7&D-DjM41VxJ#y)90SyR<*_fq5SNI1tI)iUrGb6}9bAAT0 zy@7F%qc@C;+|VI@jtiuQQIjole(-Kig~gT^r4!wDk$X@7)DtS@D~cG50fno1y-z_Z zhd|LhnP7--Kp@l83iwU=R}$uYugIIQP0^}8a!y0DC-qVXI{H<25@OR|sd5efdO9FN zpkV$FZ@R`!uoZ*O%lSs3-ORSN)dNAl=M{Z5*G&ZbN}*OIIQ*5`?gs)Mr$o-d>v1cR zhKIZ^I6gAfS;MZ^y3mnE@eYTWtSwr%Jx~2JZq*Q4V~(i9O+LY0uJoI(CsqetOlqCF zFnm{+wPBro`H)>Q^y>%&7KFIm3}=K$94N`#wbr%ua0`}`X*&~VJGA@U&R2d6o2o0K@U*NaGMwH{v zaW0M4IBRN;9_>WdCR3wo+FjriD@Xw!-!U-08!s7tu>3v2k50{9+wo&E3Boa$!85{*e`wBMuw?(<%fjH$DVSV{Z12Hw{en zf@?#0uWPvJELP|4ts2FhlX6=1)2dfP$N?-$^TC}v#{o1^TR7~MFB7>-a0AzN?9D9_ zhKD7#W9q1fx@{|E?q$*P8E&IAXrSBr&dHxHi1r7!%);Zi#}$r3D%E9DE5mscidDIy zs*4V*_5}#~`EP$6R<6`*R>qHz;ZJM`gp*HRA2L|id)f}{>2SZ@W^+}^r!0n8W1Ad8 z|N8XP2^6SaWYm$^CuPJt_0vi}@gGwl3Ta94ngAjS=6RBT{>4=(7C@v<=X+xm(@e=% zK92Keko$9e$acv7Y zJPnAK3pA*H)4PRdQ9yxs>Y+x1DtoZUgXmV7#t6(dAfA~x_aa-&qCmzxjS{UEWSc|0 z`E5u$T5P+JUq7QghaTvvUSH~mSjvmKI$&)vZ$Mj&7OX9X`MYYCcHUArjY^X|BgPi!Zq6Zq@Yr&;-!;uvBY8HaGfsZg_UIUlx#r~Wn(c^#vmO9=fS}IPZqS< zaIcx0zJ(-kO!|`D?>^wUVHgS`i;4na)RmW?@*nX2PQz>GI=_!nz5Bru2&WC!wvw{M z*`@+V4XJoee`|d5?|Bm-4{qEw`n2Tzf|FUWBkf*!ojE&sg>6M?Oc7^1vwm0R-TgN5 zthC6{V&h)$B>7Qq!G#IuUU`jo$lMwDh~_p+X;QDH51k|Zf1l3_5j)%G;Wb_YjURk@OoSEJDT6{idOUfA8J{2^)rU9f#*gPneX4FSgt*{`lfhuH=xl|5gwp#ttlMHt+jAeRPf z1y6TJ-5(0-*$svx0KlLv#F%5M9GB`A!(fFMvHzStCzIcMm4>)?A!CHCav#)Bgo(Hh z{||i*Mm*}ikAyR23VMKY5)-T594(91%>|RnGmz*kaFxyY!#Cj{d_v;9aLy(vDo5pZLLeK#ACVY-BYIwzyZ5Kb;J5W;@`YbIZ z1V{i&0qKMY@9S6a9ptYtD*((w0{-?P;6$*!Re!|}Jd*X}dn*8R1)Z~tJZ79@=Q1kH z4F?MZ-_n;9s!0JI=MMDhZMYnE&#**Ok$h(SSUd zWug$KZqH5Cc+>Wn`CDq6S%dY1n{$k9`eWcg+WYc>fyozjF@*R;$2> zeuH~G%AmEw36L3b0$%ta;DNKXvO|a@K0O@ML;k~ z!))Z4NY|U-4g%Z^fYpl@KCYx#2Y@e0aYJRg1D{t$_)_w=>i=_#xqxp(3a9?>+$)6X zv*Q{*)rWuLAJ|I{IAEBG$YNf9!8^#DupQ51jH(7-0)nKg0OAn2c>Mkj{1f%y3kPF9 zHR%?h1jp-ZIHGU|;k^gIaA>=jp>W^@MrXs2*b)Ck;+r9Xdq4bnTm+{tJ_`=wfKffA zYQ6*i#73~lVL%a7{H?;xLxOMg1&;IsLDzc#icpRE;15@*7sR`!V+!-1zUI66>A$d= z*=8b@!41~tkx2`5KL%*fpJS5mwNaD9sh6tvtkg*e={CY$_!=x@> zBIs+ecskwu^o_HGLfb8EEhhs;bu|O){tZ+`4kvc-Dbb~FcpLDIFn-)l85)=}eZ2^# z8~J!G*rNOBxl`wgWHK`{vrIs;di%{Eizga~7-bswy+Nq{NtS{41r@#u3r!AD1=>3`uU#K2oQi9VkU}bF2zwf^RE2cL+ zQRo0z5%CAWis*a&amE{9MHMLTWGLp^G#|q^#QQH`^3A(gMBvLU(HjhOR`&};1bsdn zKXv%%$+N?twGRr7%eSNX76|ljT|1(I;j@Gv6zj2E*0;N^4R%lj^LV1(`~oQ|ti`B- zBFPUvpx7`Cc(7oY6ut+-XBeyz@EN$Odj2;Pr+Y_-W;X4}V+x{FJx7 zag)G>owtR*QE$#O;BO&SgLJb_{$qHv*misTJ%?d%Z#R*tZywVFbdgj)MGXB)4_|CT z58*qvS(1tyzPYi`V5?76H~dE{r^y>$xokyN=R>mbgQ2=GUPb?FnDz}B_#2P-AH&}& zK1|?C8Y-ediFis9SBJEpD7el;rKnYA(GvzP$A0__W17FOy>fLnbG6(tl(WBg@(u`* zi>TMV&r{(}sGu52w`0lHT4O7ys>m}NxlBx8IDa!y?k>zCp#3)-!gpe+`^o9DaxRSj z;sSc0PYpU(AH?z7#5)}YW{`{4a}?K4mE2{6C~CyY#52c$mSWR%wfk!$^r_BUwcc*^ z)?V4lX0$&xphk49OOXs$B5bC0FMc>3tqU%6)EXOB+pRHFIjrEFu4IIU1zU!J;;>=* z3i4y>eTo303%%s*%bBaQ7`Ka!PuzB^4+Ea^giFVAeq4O|XeLG?oR+M|J19zwMme8j zAlG9kp*IaU6Zg+RpS9E~Hbwi}z!s{QJdF7dm42Yl5jW_~>x6938y~Km7fVAO!>>;q zKqx?$`h9A&JD5O75BB58JqzW8qGDUN``5=>AY&vJF)O#UoT=AW)E3n#} zs$#pIAEP+cDl1^yGxPU2%tPI?gt8=(y9TM6vVaOHnfXx=;>sbBQ(wcEnei7$M0j{d z&jN_K7UFU@B8hiPrSki;A^$ai0(Ud-R*3T5as2%$iA;x2SjLc>_InGW( z20NQbvtY|x`V`N9%2u9aclmiDP>s^(IPga(h3z82ejMbCDaq5mM8um@KujIB_y&=o zFeby!4A)6+s&1YJrsA&>%L^p^01qMsNG04p6M&vuFZH~xv|b1vTTU%hFI7JfS*(n) z&elEeP9g`bZH`p{PY3$u-c&R5jxFEyAit&Cp0NTQtL**n##g5YYcQKX&>#3rsX#jf zRX{_wd8W$F?|XPAY2&%YO#DF@x1DK~-C8>KRrb?@2d9TCLTuU22s2b9-Ra_?0=Vs~ z$7NfiDY!eRI#mw7{KuAIK0v{a?+ST@w+wULx)86FQqXat8c6L?}<|Z2QZF<0A#l7a#%k$a^#d`6?UTG?P&_yGwoSSVQLW5 zN$D0tIA040ATMC*{#9-lm`?e&Pc&BL@jqZ6R`ySCk~y%g?wh1YJwK{d)-oB%uT3WA zSO8rSuC3a@F+#dn<|naJurlq{*G-mYQ-0No4|i3Fi}-NM%xZVoi3Hr_U-sqxoCk1K z{Np76;Ure>Mfhx=JBF_Ko(K6^UOb?y(Nu10T(^I&QI&od1|RDLIt{4{q=$HV=di23hb8RFeE)z;wfbo$f9e`}aG0kR zqrJk6kL)!kPC?{hIo2&J`aA#r-eNah{(<6%W74qS?(N$Sc8bDA?@6bTZ+IHu@iJx; z?Bl5Lhw}&g-HvA+$m~77_Jxo?0FIa8xC?_rnUIbX9REs>#Vz(^pm&M=abvVFM!j@} zzVS&%YhUm&KbZ^XEOPBw+XUYmk9#M^)3M@Y7wcm-ij@*?u_2S07zvv7GwlUdL|!6hGF9Dz8qdb)fEhI&$W}*VnYKuvRnT%<6{m?!%4gH20^M zQ;zq`=C8Pf(Fq5W$%NyRG&n5!Ic8kmu{_i{H|mX-8O)uqE+WG%8<J)0 z-P;FOITd<5cB_<&QfdYFu3}j;bwagi6}|&c0O<_rgd}vrG88DcD}HrpObrr9tY1Bn z(dfQ32+=wc)&z;=W$AfCS^?pf`i5&IWI;}puOQLGosPbSCztNcET2%f<94KgQc4td z+ur34-v8a1FwpAs;PUNb0Rvh(bxx^mP+w&`SW5s~lvFQl>rT1JMOmi)+Hl-p&vrwn zcZ{_M!hEFvlZq7o@Cui6+jE4W^VS5*^S|2rrd~j?c+^V41ln~%mU}r-?VM+M_M0Xc z4ey{L|F11ol)9FwC4q%3hq;&zM!%E37XA|D& zGzKI0{Mgfj4FmJsHLC>!SM|*IEk&$}I*~$>6>q!JLSotPk5LqgV4_DHdVew z`h*R^ihnR>n5?lypxj=8KcM+=cZ}@AM-8`Hkq@f_XRaYoNXF>JV~p#!DdqL9ZAEC{ zRP=lWzlA4$0U`R+C!SVQ+(TI!BW0cwe~+0;wg(o{e|6%0Tj&^(Ze|T;9Iw|g%2qB- zepRm9Apf0&Tl?!&7#anBF=O{VHj-6TeS1H-)L*SKRs-b|eV6yWg^3Sls@Tw8y^H7~ z%a+v(s_3HJMe;2}_q2c`M0oQ`azj)BjYQ$mvsUr<<` z)*qibKsl}Q^9K*9zv5g!FAfO0Cv`qejY?6ScQ}pVZI(k+pD*KtX4#I(KsuYUtbkrs^PJEW?3fKz5%{z@D6wyK zNSV;zCKoEKdQon$^Uw(tt+CUpD&+S_ufb?>anfh@ETfkeZ01mawPwoHCmG5)x~Atd z(km{D6PWVKRRN)O0w(L^pLpO*zy2pieI1-4Lle4)Ei`o9G zyT#b^Q1nRAp#FDOX(qd2YGjb5!`^_HZzjzO6k7xw!bb%8wEqeRu{ie5$syS>$-6J} z)<)c@i_}`7A(PO?69wT@+?0(&B4G70-M)Cz^9hb8|G0p_kzza+|vZj@%GNDdNY%| z@4u<=gy(F3(@o2dpOgAG`YGQ@>~1In|CkpdNlj6Pphs&9)M!^uL%UD0s%xWFeJTtD zTA)nOe|0g#R<+l|16*mDWxqX)ACjKD%r7|X2lipsvd;)d7}CA{rFM>g;nZ@u^{k`p zhZPd|V%pj8AMPBtoA+w14nZ*=yFF7T?$;82(Fqo1HaZ0jsL)+2o&4Sr zRhQBa0iVe5_iBm?I<;$&H2nN*4(4;-?l)U5?^TZl{>HwN?iq<3BJ3) z#T#d@XKdZULFOlJH{nmhnBs|sTuh@!GOvsLUbsaIXSc!&3BTBrwGsB7J*JUsh(=t3g>_iKvGQR>K_G}0azS`RX>~F2wVANUDR$8 zGe;@~+G)&IL*3j+n5*JXbm(Y$91(Z6>UGi-p^FwSzqIDQBpA+SI2{LXroHqhlLC8XqK3{J{l_6oHPrnQCC?it5>7qkz2;grKygnyp|1Wd>h*u@(cJ+rIcEe-FB zY-EG`$g0r7=#^Um|BmhU-*Vr6tXr`iaab1A-vMY}UqsgqOpo5Z2yRzA^(~sn6-G@l zTy(#8&iN-iXT*3b{=RmO^#^{~7OCqS?GGrHMjtJjTO&F5 z01-N^LarFKzX_jLd44Hxr0>tmPlP+rx}IXQ#zpoFZ3g2mWLcFE~*^;;bfI!w%c1@Jji`4Fy2k%jT@CZWXZP}|Co*N z#GLXhB&@E~o5}zpk#Dv0a}^1Jl9ux7U|Kyl!q@A|syV`saS$N}YjPD6&YZE-3e7XH zTVw=oIUnkwl)Bx9i5|Mw9xx2}ok{`^mv$F)YViv@y?HLxRt5^L80X(u1pM3-!Cz>E zbYyuJ+8Nmm$rK*Eu|3S)QQS5OvRLFGEKO}fnfZAU=`obEC|8z?>8Fr$CwEhGI%5k< z9dIw=+Uo=1(!wE&>3-Q?(XltOB1ef zx_0Tb+VcbiHb8kjfHk`nLb1r3t7H>z5bDSQDqk zFjR6e3UDbETJ!0=73RGjNG^}mk{AfjOx;xVV4|DUfDDt4-(q{HLWR&=x5B%ySLUZ2 zi(5m)d`M&bX)9%}Vb?`)5J>4AYr4)O_~|put||DudBKAw?(~Gi>3vov-EWFSSwS`|CXN#k-g4<%UU|z%-HO z@sTz9PlLLAbuMqv$|3?F+oTh;h^H|O4wKr)PKqPl$M;Zxj7YNrn08Pi}CnA7uYFEsBtw_Rpyn=bx|LjTLaL)iv(S?sfw?SvD$;n7pwiGVlgqs{_=Fs zwv?JwGfeFnByZ-!H`j#h;`Ej}vfiF8HWR+w!KQEE44zW_cmbl;OdwG#bOputH*{OC zd`d?K?2$G%Y9njDJ#ajy=J$-ANT?5X@A=1S;$%?`3K^K(b{|Jz{{ubXMQ-z(NDU>xz{bw z{>6C}9iABDqSHd)L5+MA2M(ZkIRe=^(28spZqzul+c?}Cp%m;2k*a zMmrSn9xQ64q8Kk!K5>#>{3SN#wNN>2Urtz+#)A=COrkmqLk||L4Jyb)$zA4W4d8C_>GC=Lc$) zy0Q&~BGry$Dw5U3d*H~(Z<6Ao1Z>%=ABlqLOd$t{k!EZ_+8eLmN zofGI_GzVmu7#4aHBI8|ltvY^^d(*!Erz7%ZzBPIYavuaegg8J4pPxCkJ^_}ve9}=b z^prUj&+2k>_$?!@4{UjnHeR1HO{gs(lw{DM;BY8T+ivaL$h>DX%9=P?P zdSd%mcJ;Tg_@ix4CRdmq`8{!tcnR*KB$&zPz=L#SF{g(*yL3Y4Gl4fA=i4h8l zXqJ|@_M((d&%l136Et8V+V-MXpoYCXoIZ`S7u=cCrIe=RtfC=6+@!j4!LL{HtiyHh z$k}ti>N-=)iA<3;rFcf-ubUeYs>joynm&6g-`IE^eW*65Vg!5StJius9TKdE=G6!S zDKdg^`gyew8ZSAWphqX!U9iz-DyL+~R=Udxj3>V(EM|qhlp@g4Vy1t&)L*eP;F_L; zmli7y`D~;j7}!hSO+cxl0p6l)mWm+)J#o&NAIvN`WS9eV^2#hhR{}^8zi0D%+7o;7T)oUTT`yxG z+R9}h9J92$*r-=-ff~J=_Z&g`2(r;-vpFC4Y5J$JJ_U#~#uBASgQv_V7KxsJ?o6&b z7wWWyUlll!#m>?vAnIor4WkkW*Kz5cdwsBavA=4=@c<%8-+a*06PrEhy`umndL);g z(8<5wSi)E)`81jBQ>RMx!$*xtRU1a7DxdGvwq&b7>ZK+&_ZJSQo=MvJGZKqZpY5WN zY1c;eM(x~h{9^H3*vS6{k@DyUxnyyCUu>B$c=KWdSQ@R0f9cg0ZghU1j> zrYwh@M(y^lgApC~6dY)F?&~km{>nW8=QMBY-8UpJBH@9u`lr})}Q#~Zc7Uv!~3`@Eqrj7 zDRc#5F5zkHz26FGIXfxe8+-$)69v%Y^~!E_khx>jW)|0#(UJmvc}A&u2Ymzuhp()o z>#+eC)_;Z@kVd32B7^JeOCG|H4v0EW7QbAhNrSfNaK4fkuxpOAx^ImV#}nzWMP%m8 zd3(=E1>qA!Bv&`~@t@?R9@K?-ST?A{ZVh8LnvCGopC4XtgVka`^H}t$NmHF?bBsEK z(^fwRL0nv~#AL*tnjRL4OH-+0J8;cb5E%zfRxc=?p(U48=K=xNG>~v*QbwD3EpTb; z30j>Ae~}$xYNlE~A1}8MaJ5-B{(asaK6^kAj_z^h+IN0)`TLPWV|?AuB6%;wl%pGi z?6%T~Ed-tx)R13?e$R&&C{tZvhdM;wzOf*beKNvEo@4iG{w%Cct}UP7FQKe&%1xR$>}m~V@n$=npDq706P+^T33~i!Ik>c zm0SYPy?&!YT34V$oGn*O6~~8m74ri{8wBt_A_Y^&yZ@zm4&S60e#c*P5~J}9@844V zu8W#_)>?Pv3o3pa>90DjPc=nYeb)|C8Ny%g+&eN@BW%<}8KnuLZ)ZsB!83~6 zbPX=3-$NT|^>2uTYa>i7f4gqvTh`+)5^Px@Nj*oqCIWzd8XqX&XC}OPc>QsLr2jvg z^uGw3H24yKYvNPzq3eYdK6c_Q1m(kjPuP$5t*{?184zuW;n{~rtfc1vt7z(dMt1`k zFx)lapp^M3e7>!*-2rfct9-qV=8}XrEDqKhg6&Dzce_A*f$Q}pXbxl9 z0vw^Q;45?=R#-2sO#Q5zYxG_Yz6J$5VA4bx<`VcsH*3-XMxn&r9-99@h4TM4h2nJs z1_JWt<@EiVa_e7Uxd>Bb=Cb*B%Mk{{a;nciAw0QRO@fbMv+XJV1`j^lWZ$lTh4X#- zW1<_t3lW@e4z#}-Z`eja-r#KAmJR+_fwGcYAoj*Hu^r*geSRQ=bsxX$#vp^$WHW_lOHxX!>M$c9rtV>-Gf+ECn`=QG2q5HGK0OdH8Bx(uD4728 zHlO2#@@GdMf3+4~At-Mm-YFcBI_O5ZeaU)!hS5Kt(VxE+!ii#i9{);-I-1Wp#EPkw zA^Gfk&ow+?{m2KmnrkB^M6ty9IfEh|gLd+6!SK%aciA`@_Afx3zJKza>l=#1!ccg8 z?SnqhRbM+d8QT@l7LUrW^*d)7#EA-N9S5~@@~h(Vy>Ewef3AQ6odpS zya+C6WM4?`N!L@JE6#F=gJ_q!p+esqSFv_H=Ux|hsw_<}4wpBl=IxRlAaX6(2lDvt zwhOCDrzhtx^d3eQsIGu2RmX8b%ff!aIG{DqvuU)}#pyuRVH>;WM#kiO4 zR_)QBEtP!PA=3ciqN(HDg=3&}Jd-KXT`nZQ6;D*N88K6JfCp?I&@T1|#6eD80u7hG zIH%DrHvcfTM&|+Ms);27uA+`3M8_rT+pGC+MzR6VSwLanJ2@;kygUET`HwhXTT0{| zo!DG;E)_*?S12z5hnu<>I>>K9cy*r<=#OjzY;JlpNT9^b6JB%CLU;hKlo?l6U(Cx1bzKL~%3>7cd2~=4bRhS%E)VD)iy(-Rm^xGDUEhz0F|T z-FBdXf0dhCC4nrFPTTXwK)F7uHTzna;7+Q5nIYJMN4;X9jLkU50# z&_cD|hnX*|uS+GzUg2~n181?4O}18Dcy*+_*oSz#0a^WYjM@wr47BF7$#AfG>AohC zOcdvrbm^GT)Qy#RZ*XBT7OQcOe0lDcW8|{~)X^s+=tSQWyUav=+I1A8+WWhQ{%G-Q z>S@gyv55QuOVk!hxqd%D(?l41KvPGaM)4oNkwYS#lbV#5UcBsIVbIk=|KulTrsqx)ymXU$HRQzV$8Dw61t0`=2r)H5z6*nNkJt=z(7wG}| z`s;9N$AhWOpl&Knq4l{AW>E9+fGWA-lsl6b}Zv9ShJ+E5JukKF1bVG>x z`3%9fWNH7RAEZR7<&u3=3NFLx>G;f!59Yost*>36Xs;OL5*PzZI3Js)fokSha0oUN z!0=Wqj*OWv9;>gbfUdavtJPmI5K-_}qT_-kPAw(Co_wj(yUq5p+KAI??y-`4t?vNj zMaPm5aJ&%bOy8iB3d!w)6~4gf7yKi zsQWlk^Hypmo6?TZ za%Z)!%iLODJPe=&*TV1T0K!OqSAS03Sj5LePxhr-;NZ>R`1EoJiLB=2UUFC*DMUf* z_bZ|e#4|TJo^To?d-b|?9PAd3I|Ljic`u#4rndL9?@)qRfk_kw@FLKMZ8Zqmk@JDp zmz!Ba_%E}R1(FX=l>)aFCT#AU@Y5a#=o?)c%(K7_;uA^2Pw?(s|?XcPXtXM5V ztuLTubg#3;al3-+gU={({+_W;5JeLMk9;3<3qkU8TUV6a`RE~yEoW2t{neJPH1qEL11~wRsb@^ zY@SSKnC(BwkuFY_5kR$tT4H^Rpnt&Ea zSMudk3N;H{VYt+aBhLX~&}&Zn@OWY&Rd8NmEm+STFBv$noCm$qsjDHYR>F$3L)gns zsnwlfFufwh&tb&^5b?F@DCS^+ZatFY0+xVY1K}^Ct6nP8(L~%QRJ#wSsvJY>K;0o) z?|9Bg$A7r#BbOQ`+QiD1jHvS=F=IUP_!}94@C4Igmw@k236Z919AS`|NbOm3=lY^q z9~oknda1;>cR&{?rs$}DWxxnD9eAVb*b&eCHWVS>c596YTdZ^bs*wpA(K5viqf`v) z&3LOiT9qxKxp{BU6-4DO$J?!E+>aFKr~$ zP|*PKB|CvOl+TIXmaDH_l%XpTx0W!8bG+1!8E%fX@N{WkUlGIwcaAlsxCwZPGBnA>l2jU}t9cJaffy~Nt zyE_9rIEs5D?u|QNocnT~@YY$LKxk&{N{O966+D5E4U7#= zzwd)Ip=@OSeW9o;M1q8$awrH@SacUIRs(#p ze1_=)J0!Xxp7B+<&xjOS`!GBx=0E?X=e)UV>N!PT`d_IwLzo2xG=By!(>l$Xx!r}& zW|6j5`9Nx~#wh3zYFIo?A8#<1rbDj~T0ZXNJJBCmETAF{#O;_}&K)(m)Dm#BRSRj+ zmq%V;<3K_y_ZjUYW`61Atb3kh^J5!`5O}ykU$P0KzmCN`NH6uV++lcJeX`?UsJKkB zGm>wD3ngSTnQu|3l`~fn30NC)W^}DH{X!rNadcAh1lX5H)N?@6oUC{TEn78(lajd5 zHI1+~5Cf;i{E?5&-0R;2O)sVd0Eqh&|qCqrZ}^65Ah-K}|0=jA#Qd zM}vM?!tqN{A34(Z<7)w7}fj0%l;3&MC* zxH|!;GBuc3p5yB)ZCEQD|3hoCUNED4k1vM5F;l>zl(V{{pUR&+m#zsIIWH63%oAA^ z{9+A%GA};2?=f{#frliVvBuBVhG`RlorRx6WgkI88|E*7+m!%w#F!k{+@SQfotdV|X(Rk?uG&WDYh&2ZpIt9_TtXFI}7m z9e58h_3sa~Qq>j?@R0x^m&teK3HfsFZJ##BXOt(WicVNMbi-6QToK{+x_DirF{;WNXM(d*FR=LxM( zf|CE9dCD`;*#rzXhzr1ru}~C{bfH?fJyHB3zo#l~FiTn4i7*e5MHuY|;(_599x;Gt zLZ}t@55>(n3@bm2(+iy%?^?EcGL2A9%oFx2<*=2RHTv5$wb14w{8LaiPe`DXR~FyF zs4`)wTx_UbiZ2#M%jnk+;-i*c9dz~vjiT-JF+NSaqbVVpAnac^^-Xn@#iQBXIs}SA zfBLrE7~>bu@c2%RAxU&Qjb4UZ_N?ATARMZ(Tcckcu*iH$dGONCi%B4aeQ@-v)zoPo z>-&E~ECmyvV|sP0{d=VZ>|A9kKIDJ|yqE5I`}z(PQTc9z#P@U-^e^-rFGH=UC8_*R zcZX=uN&atQetAd2xRkUvm|sYSb@+otR1)nK#nNn}rt0C3RUuU#Q|ah_RQgb)mTXG9 zV%~hAKpDm5rI|&#iLAG+{AAuZcU6YXC|@3%K-sAV4<~^lMqmYS3*EKX3(wHvhJk(W zLD$&*&7i25t=CG&e7Zg{7Ed{t7m^^U9XzpnFEe&6HKZdVJ(&{rOtsvo4fi65n8&cl zNuKbGf{vGW%^Q=Km9qs50azw8Z083XY?^9q`dK?|=Ab*iWySl8$j_k3o%AO#x^FH| ze_qUPq5FpHt2wtgD|^zYLat+5W->9=IUPZz@B zcO5gS%ewF@>}@zlO!(;<>4Ax44?r4BBKCODctai9rE(z0XBVo)^5wSFXSdap2 z#`3Tnm4Z?oeMX^RK79k~f$QmfMD}T86X;oXIqC0_&;M;sbdR4A1o}kI{i-sF^`C#} zp($@kY_b$*jc!Lgh(296UK^xdGvZY}yN6s>`!5Qqx+jux(o>SD*_l6A4lUPzXCFFw{!gU6>P6IXU9Kf~a6>>xR&2;3|a5Ph}L@AKr3?9?6gWmF_BD@XM~* z<6s=_MQYBYU7u5v+hHGIP&bzU(WW*f@%@Exng!~b=lGd98SC`2|GcnuT%edc$Hxod ze2A#L^vW|Gq8}ZF6;_zbNzCjNfWE^?&)|PB!@ErdI&jQzMN4NR?MU4|+(ztwiW$ZY zL6M?Kyu-a~-bH|xBm#ER7bz_{&k|HpyM3uGxv?!5Ql4ps7Zi@N>JvVJpJbx{Es{DK zVc?kglD(Y+iJ+bc!6O((2Zj0K|B)@MP%Fw0{OjTv5RT)A*Nr6%V*!RtC$-xt+?U5r zn4jHng|9|_^*MvqD9Nudy$!AE66{$SJi3h6U-)u*VPRh?gV@2FDA$+upqr;n?}$*i zbOWrEsd1B&nD+yCI{jyHyqo!hJ4odC)eBi=-r&_MxQ>nNJ!Ozx5a#Of4Rej*P64ya z*wH#0=c`dbQ8tv|eb<^b8*<*PjXVbB-27+nCD)U)?BhPQ%@`_YPrN<}W5B~uW?Zaw zQ4O2>QqUdrR3=W7)g|k4yUD8Xd5xItC1R5>mEnV-*55;jDuxfBzlmJ3>Yy<1}qL473lqVqZq(8$m*<-xM-%E@N(+Y%rhYPof)qqkQ^C3c|3`HT~ z5Dar9q%P{@bmNv@HOghO+d4_wcrR*JE8X+uT^NJJfS;4| zN!hJ#+H9WC5!+_2IZJ5sNi1@TOW(2dUZ6JNL;5 z_3n?KIPx2R%>l@2-k~yBeC@*rE9Ed)L+X*zwmWmAbf~tld4||QisZdH<586ASPuX6 z$~DT^_uo;**41dXm`4i}WgmXdcFegI`c*rakH{$RQ0)q?GPV{G1d#$z;;}v&? zipWR`NG~sra>6=BpT`F&W;Od+-MzHfQN&CTKa$6RqNgt405!h&u`d!a9PO+r)2{%(nP z3x0e+gKy(1Jz-;}QuM(!O1U6ofP_<|QLZ`0_nb4F=TrmRpv8QgS&OXmI zi({x9e+8WvEj+rK{%f0|X`r>MR2#@+{f`HhE|c6nTP?XhTmo~F$3*mTilw!9Ik?w% zv#p|#juMc$yA!NHCy%k@ddi*Lrci%Aw5VEpa*KAP}MreL0AP z=@#>fZV)*mK>$L@)w)!Y0I8>Zvyd4v0z?J18HsO@TYTr@Pve9o}gA{}%$Y3f8CAMR=%Ld+H8tQFiKxakMc&?cEuU~gm&##sJo_OM$0YqUa|n&=+A18hAdv0sZe*2EId z5O5c(A0#1|!LP36fXq2(7;yZbEgUnuJkp7sgLx1OsTF|ZQ=o;p`!pMZt5H5(tzBpJ zLA&zDXpis@7vA+-))IS&;inzB7S)f=s{6HWjFt#H-Fha~N@uwdbV}LyQp}U`+W?>% zjf`6q#cA~?76UFfsrF2AqYz;ZBUBKJbh5}9!I)Jn1sG_V%{Y{yy40ib2K0={hF*M| zLWW9Dpta=tKV|!)%H_bo_k%K$YIL`hbj!$QXO5vvUtaiWj>RNkH%BXVam%2?~d2!*qBekOx%Bfd}L+7(L;<1+jzM>IwJQXe99 zpnAl-6q7E*6mJjMd@?jW2+51~Est*fa=viQ?QPkBz1>jiuk2XfBwV#*a-kN7qv zF2e0%7WemB4=9(8V-H!vK(_2*-U6J2UdSeS@cYyAp)XqelRLc<32qnOeW1sw%b@&C zJZzrLr_{6#6wGU}l(xib$1-0KNgwyD*FW4Q(yL?X@>aU@pH`P)H~|`WBKDObV*>(R z68atP=@I(meR7lAr5F4hAVP*xqkjcOkZVH>*MrWDqr`J!fmqA0)8=8vB4hA$fP|mn z5MdNRg)ZMUaQ;E(?kPBxv9V4WGUZrEr^t@VMO_cqHX~&p4RsBjV@W7R$F%LbG{6SE zeEs`hvi=9CGh)x6bU2cg?&c1J56w~R&|1s8g97Z!8^D6gDX z80i{gGDHCv+!D`l`@yQ&47P-WNg(87$7v$t=Kjr8MKQ!|B%kkFXA@*t&&S4uWz=rjp@*i>ilmgQsJ_zF=?`5O(INA}o-b|!8^XMZQvthfgM0P6XyL>dmO<0QZZZ-y? zYVHeiS7HbKMPQ=WKM(!pBhL`3|2Jbs)IU}TWc5Sk7HPJ$4`GeG!Zj2o|E%-=noqg>C>z==Qn{Ppbh~e`F8Y!7LtU~HnzCclhjD^tjPVX( z(vD2#Y}+X!7+8cSmc%)KyRBT-kFC85bDXOFx5nNwtjg}&9+pr9B}5P;1*BUkX+c`L z8$`OK8&p7$R*;4*-6h?M0+L%=y1TpKzjk=e?>!&hbDrz@g4c$9uY0XEW6Uwfd=YuW zOT~WMv&p30j>N)dr`&7^_0#Lg80jUqr%j@$#hzuqBZXr8DkE%qhYH4z#5|gF$peiF z`$a)=1D{VbQEZszjoTS2Zdf0$cy57C$a+?2p+Wcx&e`MXk9)+pziSQ37cWtw-Q1Sc zONLE;)+x3IveaWSX|$9-E9_S7!V~d&=(6*ppw0a-zTvIPq(H~T-W&w_pG?%S&`o1; z!K=4Fsff?N4Xl~5H(j{M9ve0-NfnLv8kTqdW>u`jfgQ(Jk)kSN@&bwZB_n0?*f(SsM0!2sAI^5_)s3!4#RD9R~)L-X`&eHRv)7s?_4==!!H9~q z1=8Q3cU$6QGaEbDjehy(7LGlbYico>7)X72JYAwR(Z&4&bUs-vl69sEk?M0&aK<+q zCCnC1l>dkpyXg8vSN5cpl3h zC$VQ&dnlC+NNm?XZnxRUjb`t0`nKFnO`T)^Y7m)3e=MG~NN?97m=(KD7@&p@oKe zMMy@HF(~+8w$(ZIge|0w8s*Hr&vEjC97qXm(00v0TLVt?!o_i!jXZPTrC=4BAX z)uC4A3>4dpUOY6Fh8PH{tI!qKO)3Q+qeUg@X+NE?w)xs#;C?z|y#oa0{pR=wt;C-R z88%5-&U#1MRtnp8t-gy8)f?Q~JC-hOsnC)7dekC>`^_HKo~p3zb-}qGEw-WXI9|w~ z1GkBYivLvb#k}Be;ewL>EarA@%Q49l8ja%l94S#W$=-$fyatTXLhp-bLV_W;--o0E1 ze>1%x>#b`;fOS~>&`NnzPskr;JKR1mWB0p%;%}H~bs6IwO290wUIf%;y^@Kt)JvXS zGclk{(AM`DJL&MAvcmdMxzJF)_80#T_Y7o#PB@YAA*BGFx!()#Qe2v;)8S$^?KW&8 zKU6j(UANVk@ZRgBA5C?(>@gtllluP{{LM4M&f~8YQhFsW(dvh{vRx!T!_I=r_d{Cg zPVbBS;;&Ul{sRT-K3~p`*IS-L*a*yCQrg-ysVWK9I<4#|@AC#sZifLS+5U>W3<)1s z(v1-GB)_2o^-NzMfzX(LJl*L*?3K?tfX|-;)Yl|wLx}EO#)uRS&E|Tso+MG$T3Lej zS6_A(F@q} zK@28l`ZX!UbrH?sld0#Cg<_?(M{$ymP%ZQRnHTmTKsLpPm=ow?TIEatz zPTxxu!)i?3f9=3L!jk|61^z^kqZBqfr|3?nPu&SpT zl;tQhU!)pPKjIoDExk!(vfFz`>2 zxqjs0q`RsTvVS*%(qv3+h@3EI^+A|_*}z52eUZ&y7}vK}|K^oETUI&2``9Mp_r6Te z@<(y?Ezkh+mo>Ho;8TW&R6s5N(c*pB`G`!duvgIE>e!aA4z&Q5qN`Z?U3a)H+7e+H z2!;!KlLWMlTxp1%eu(PvMOKL0wf}PYO{3&Bn;eASdS_rk=4UtIHzBvwcKkr(_=v4-3&YfhJIa=L!;{)OD?sk{~qXQ#v?^v zI3~Q~b&<0kb?E40w~<|;Y~^>Nth%}#HovKHSjS2~j;X=nn>}+M46NjHx{q6x=j~7| z%wO-cOkp)T=1W6qH)3@Mreor?#1^Z+=h*{_qdym0Uj9_b;hsOuW~Q`C0t%X#*e^Z zt;vsl%AmVvk3+g~FF4fO?@eRVg!;_^GhpbS;cn0$#77iJh<6vQSYAV4*!EA-AIwQ-?;EQ3^b|4$hbWiN!mPq z)eaS$Fq<5uGs_GsAq$EbHXd(YGcAM(|R!U z`kVVr079^$%9!$^DKA)a`L=urd_ZsaQH(Gwr6T8N@yk@M7};0`rVk}`&Ub%_XBR9Q zmK?^Oz7Bb*X?R-S)P09mBIbUdJ}dLjcQrt{KxJak)VL5x(0q$u9}(SjXq`bgm(pGS}Q-FQ@B}MC35T4 zYY>9@QF?9LQzOhZtHpv#~lCsXt90RY>lH!By*2r~x}bkCzlsyzRnIw)l_~?-chm~E|?_e@skQq9+MDIsSC(3yU<1~esJjx2o0 zxhDC91sEs&w;uy>SBQ7T_%Ah6FdKop_w3+fq8X9vm;r-pmSdLYins|Hv&Rm0%lgQ* zoRAmxlL=h%*uG-JdRI&Nk{DKTAPU^;E!(QG}(?Qo_`z$FR z@1H2)N7&N3Qv`x$V}H*Jul<>&nd=CBL4H9mMu_{GLO@gDNzWE{{Lx0IK=?fU>#oMV z^pX2ijh?tidmd{QY}Ud&V-6vn)$g5Yldsu7gLC&q9 zE`h>WfbsJ7l%8bY2uiP~x1n1dxfs6hK1)L>LN4;0+nT!)y7GtzzukOfQ+g{0PC~R> zx<`bMVvu$4arV{E9^>kyh9pm=TQI^jH5f68R_x_%7id_$=zL6hh#C{kgkl_aIa zJWQ&k1WC88y20W`O{x9Vc^Gz6FX`4jT#AcfX1`o|*G(XXU9uOCVfZ?j1NX?LrK7b8 zW2y|JY3g;IE$9$$jDnnnR_uC29vx|C9;w1Q0KC15@AbdRf z`*Fb;?~y}AJD_QC>na1OrWqpxqqKKshAYEA^#)z*`qX{NT^XoMDK{&=Lf3riSdSRr z>MDT7upD>e{FSQ{+4&W{i7H^hgrwhv@oQh)gx!;4qs1ks`eSeT9$jZwy~c6J2vRUB z6fP1FPX}u@$fm#Pk8C!8X#jv`gW!}`&Uaz2So9GsOt?ptnlubCYV`(sA)p2$Is)OW z-6n)^oX=B9EF`5D44v$HYrRd(^p87w8#aFQUG#f4#QwZs0b)M)-#h$L;N(XLP)EUK z?-@T~xBVOZsR4xDG^qdeexOVvIun{6()=UKSHN`FD&6i9B3RIGMbYu4aq6{CMsxDS zgO*~RO1a8LF9`!Ng26%9K=_)~t|&}-UXu4uNkk}$~;L+4Igj7r(Vbe{fKJC6QKr;r(5-udGqH!4Rh49kFy#L zsoz*)>>$ix28`R` z08*n?2N8XZ&G{a4qTs0szvF?IRC1~(6Qw*ck$p&y%X;Ob2mVD;*+CSU7pIgND3|s9 zXt=NOIu1!Ic1C;u2Zyqpn38IbxRl=J+J3wyn}4K*Zcyo1Y+-U1=D}rm z!3FVg88lT$5u^~DM+F)sx90_D_U9T|1neZ#UC=2q zU2S|x-FcuSS^lzUOn$W;q7c74EP7}~m}fZMOYQj`gT`Xjyr59~*JACgT2g;bSRqiO_691geSQ+T8TqVpS#0_f@Y!C(zC?2pIS(pQFlmlB6} zIj@2!BhG*Md+e}IQ2&+MV(wN{^F66~U2YrJmxcSPjk!rT@7-;eA>T5lR1Uq97jej=pP#);5GDYN7 z{^QX3{Z9^^#)QH#6T9LQbKhXlCd#0}2H|rE$#0%|K^J&XH{S)?R_C0ZL8B0p!MAj6 zO_fTbu$_h16ly*~7leTJ`l`iNFEaLfR}bgQjT zvKXWyHg3hic~)f|DXNrhc1u6_d#8j1l>YySP0yC7@@|^wZxiLbcgO{#mgNW2kZKXM zIx(0(`Cp`NA5FMA?Pq7bqT$bB4znJ~7ZdVwel!)9e?)4p8rNH-kJ$zwA`Xqth$8_* z2-`_87+zTNTXnVa7EDyI`P7GYPtqYn3WuP>dc^1XWSv*b@P*xYw6A^9;pS7GG-jQO zs9>CmQJvC#Bim4Dy_>b1%hXOG{_*mjI3W`!?$l%0nW{AE=9=`47)*G>1ZH~}IC|Ud zUgq_FYXX$3|I2&#cymqkek||~QJ$EDJ>-`wU)D^8FPK}@?{dS3%cI5w$Rb28@Cbq9$6-G`h@XK!?+s*q{$hDST-aYV^s^_JU1J!Mn)T|{?d_xH-q_G@EUbv4>gg|h6fHF@37t&WAQ zb`9djKk}p*jSK7?(ox;j<;7-E|HdGn%<+pL{1UV6N3XwpbzZ>igV!NcQ~So7q58hv z;;)#>IB-=;U&{W7@3?jT>|3uS2Xh!$02xI0RR80);<`VSZRdJtdO8#dCH4Xvv(tgSrg~^+Hg^lT79p8^V}~>$bzhhVRBn!DA6kD z9skp!7#CVf@ciW0N23daP`bg3Ny+q$BHijR^-_`kR=K!UV2%esTXd-V;q*UgxLoxD zUL~l3|Ior)7su)gJTZzE5wofN2*vAnXSXaP<8`_JkwiyO$XG&pdw8y8L{=FazzCg>!j zVRk*4pxmDyxyJ7pur^?+!{C2*oj?L`r_F%U$pBzTH@K4v#*4})Rvm_v3ye0|-UNKa zUOYWSH#G}j4CqBsQc=FTJ(57tX))|{x>urBl`Q4WO>>QDGgcb^#LPQN%ZN=Q{XO^S z^0Uynt~ej+{z$UZh&5iD)u$ziGZl@P^S1`}p>cQ;cL;s8i^Tqm=wPhQfn!R6&;9BJ zvUf%FvF$|77k-!B+Z(PPLww!-=TQGnTi*6C66xbfH)J8xpxHMCxTXLQ zCAG%Rz4g`4vr>;#vOWwaEi}?=o?0KQRb`&df5+Q<4)s%j^?I@s5~X{H|C{y{HTde9>Iz@)PQ=qGQ*{1Q8Klbp@KbJ#k~ zr$^Qt(})jcY8l2Pu(8wkQ)W!3?_ZGjYw%5ke}t>5hW+4+fN9AO^P zVGOiTe=^&enl=(lUi)hC~G>GKfX6sW%>}rZTeM~$0VdHS+ZYbGk2Ff0G@SiarNHx z*Id-$Z#WHp`hK&`snyzuUk!KYv=gJNeywAm0cdlYZg5mLgcWHlzaGtD9H(nCyL%Ij z*#CJSL3U}tsz5%*o6{D)7R=}Op40rz6WnimAF!>IbB=|s;;%3E6=xL|S_0*sJey%& z`Z0Bg=`WYJ|7eT&&cvFqi2->oC%9!G$OjrNV=vW8V}Nv*I^42Pq-Na zz&3dH{0L@cp7VrmfC3!sf4nc~Ew;Q_qDyT>C}=p6I}AYEk;{JTH77f`$7~FmIED@G zA3K}Zv>Tb3)UITdyEo%w@=>d*L0}xiW6Q7n8ZdTsKrP}P1-IjkMgq;T!p=OSjS?s* z_hffOe@I}iBVy5#1z=3`X9>b;edr;& zm5?+*;268JysSYU$4#~l7C~VLg-6JC<~QIf#vrZx4uc3LBnBA%Yw~~;EF4>%dl*Bn zX!ptuzg4z@gsa2|Jrc+PTO9m;mmUrPX*NZEhmJLeyAB#kgi5TCK zPZDlt7Wx}4x;f;LLE^GK+*^1A)6x^zZWthNF%^6C<8Oyx1tNja)L1=s0nG2ggUzEV z4^Tq9IUmD=JIpKU2*^vqQp3IwgFLwXUz!v;**Cr_ZBvx~Z?6yhJ%% z<#7I&b?jxPseOB){38z`<#Y8lJ`aX{K7cVR%umZqZYd`a$%Z%!l#Q4_r=f!^a_~Qjj~TR5i+jpog?`=k#{kyBKOscR8r3lwqE3^=ESz%MY)3MTx6~azk(GK zjOXq?og`6ub_Rz_`^M$hbwkHeVV&JTVz7?~dmm2#bef<&jx*X=aLPR527-JtUbANt z`SagC3_*Pgef@M2YM#NJS$$e0T-pJgs+rF)_Ubne0;F2hyIg7z=xf*ZGat-;r=X*m zieEcByjB6x1<;25Y1S|J`8`sJJH%GLQmAFMwx-=RKH`^b1LW>&3G%h(~`jwh#> zB-y^kni!}RvEcU?lpGa)c>pBY2^sZ*k2c4*2gm~|!vxy}rSY2#IRVOr>Tn7=L193y zLEz8L&+kcb2Nf&*ffC$&Z>o^gmjW)ejEfH`RYA}q^YY$>tE&Tn1QdMuNkb8(d5DZ1 zonb(d%Mqpv0tEtN>0pS>v&T)mU%VD2>gFQ^Ua|l*CL5}0%K@FdWd!F~l_sY&atHGMeTtA=y3gqwof7ww1g){rAhgRP;z0!uQStC74 z@ppyW1>N_f%-_`%Y6*urF8!t|4;=wlCaq0g5JFmlPDzjCvWn@{QszRe2J-(h&qE>2 zqJ+#N(K#rF{+8@UroK;RoYpZSJ`qHCz(JmKh+HNz?|-WSGi<{OX;1!vJW8k?mb?(4 z$NZlt*M@Ob|6Lg(1AQR~EQ#P8 zMhMQo_j3z?cqEU~vB38gp_4S8-WtkR2cT5V%buyt+)6vOTc~N^On}fWi2puPJ@^Ld za~K}#Z-KAGkGzTlE2XGM=X6AvWi8nB1iA9? zGQ@KPz-HS4pkwXjSGY-CEJndAA^IOaXFP6#_YHs!G9!A@nqz2qCkNf+#GMYjp(IN6SU#2}u_VsRg&`bxoFEcL@YpZ%JuKvRi6@)qv*r0Ow zH*6@f6C4!&P9b?Ji)RK7jECs^Gj?X0e!_9zOuJ+eU1Y`goM~ZVrimq_O`hymVywDu zcj_9FP`l!Kvh|)_%OkZkC)Ms9J(0P*a^`>JtvGX@IL!>t>2n~03rLfMf(<({*!s^2 zAMIR^*WceE>Al3A5lM^P8^j;`kpG~vQcqqyQ$0Okj@p$PoAyYn;MH{gZL7#%53+7& z$#PXGHD;A%eVS`i&;O;NcI`(YFL80`wT>OIcf)>fy|)>E z^YW=_0|qMI+Z*T&2H&F{RHadKw#I&k4L`O`&+5|9JxnCx4Qy!<8{OT;rOf#C3(9yt z0ex^NE=(ohn%a9HPu{#rX)t+Rk8ufEYAb77cG?G3zUNNzu!RCw>?;!BYHRX zLz1M41)K=qu%SP?fpS|^G~-6ejp8z0wV)gDe^^n0y%#xZ1`k#0p<;FaR6!51ch6Y~ zin2O5TCqN~v`12^DvINufT(XcU&%dan9Fvped8odQ8;7MFo$VazsBem_Z{2r2y6-x zIjP80wZfdrQa|U%*yR0jDG!r81Z=YYqRIyGfj5mK_iaF*b zbXgsXW4m%3tze)2?$mNddD5^877LVu|F4U+a9g9xC*Db?Vm!{FVjNNE(@ErL0t4tx zFP&q*m9zf|WfxYwCnTVfr;_#8<9KT_BfF=NopLAAv(j>MdAG25r!9A9DYt+5bh&Gw zNl5oE>Bl(sWhM6Cc6G8ey`mn}Bu**jg zTZmouy!nO#aiqaeECZB@cf}4^$RqBCx)qYtLK-ih6ri-&`GsM5jaydkJoWN-${2Up z(BnrNvx=#FA^dJUO(_j_mk)GmECS~kb?~377igqyj)$#(PZf@qS8g;R7+2F}xGHxy zUcH+|izLl&W^VNM?cl7FHnFr_OU2-jPxSf*Z(UuU4?gD7 zn~KT5+@1fRdTiI`WXsA;B`<1o&eXcownv?`=~wGPJCv)OGGw_AHE;$4fszi3$O(K) z?fc#*Z0t<=OauA8Hd_xLsdMI)k>^{PuG7kjmgksV^9-5FXc`1UMqHT3Rf&;f0cC$; zpU!*F24dNq5~QU-{FAUy1b+la(an} zmsw?8rgM9SuLx^5Xv4~%G@Gj1Pu9s7=_HkKGgTRHd#%T+AB*L`eCg9Fgr7C-n}#NIxP#921J{iRyL(Y6 zMz%Bh#8>=(vlZsZcZ^$Z3GQdTD8zm)*YY>PLGobZ*XOhrJ{+ZAbpHIQC}-5WTca3SfdlR@WLMQN-CHM zzJaQMxq~&MfQ?Eoj<}=F)H7=}9&7FXESxOYWpz=K&npCldtOTn>;5nM zRa^^lfz;>zw;~B~8GWrcp_;}1)UhFAMX7sqK)XSqTqNap2(GVJyfDdAbpr_pL%9|$ zi|>67vVQUGRJ%?<53g~~-JQ^iJcoWuk@FeLrGre_%Qs5V9Boc5dl7v<_s1Lu$7JCa zsIoSL2YDSCVb?M5`OItBQt{rtRY2E!$eU%akA=xZrA&KiqeZD`Pdvr-KzJ`;x1okn ztef6tLNUnT2FK{Vt#xXyK35e)n^J=eKXJo9%GfAxw{=rXfs04Q~fqhY)d+T@+y z;n|a@^I2vC-H{!yD8#ZJ^h{@+?uXgiW^XBEe2g|5w=wipI)m9GTOC)rGgUHPl!;1z#)7DRs9q7-;1$bDlt>s3D9e}+OM051Q)$z7rs;I8dsofm zU95xW+?CCRf;do-#7P?3cou5e6(=xSH5|a1dcTcS+#YHjUR^HX0>8v}bO_)pzJabs z4^DQyU4X?ygi)RmLM;wU+LbdO5ZFd9?-<7QhQ8_t)r-u(Wk0oQxs3HVXC%f_>N*O2 zHHNj<2Ha}26UI^w*JG6%>s+*4Sg*+pPjU_&KKybC+`Gyy-kQ4fPYik(N5MfTJjoYd z6@GEqrR?h~%zfLAR>a1|P&#CLV)v#%YRV;LNHE!}%Ho5!V^`jr{LK#MBER&Qu4mkwlT%j#Z= zOG~54ak+=hXEZHBo@R6L-z*(k{2D0i1)$_}w~ zlF1MYJn5CWFQpRKyQzKMa@N_x-&K5-OP*7|4ShK9D$mDjp3-^2IzjGQSFQZhqC zr6Zx%=39R7_h4R(+8c;8GMZ-AX zKESgciqV?I9zc0=2gj1;f$rteF}obbk-f>sRpw_Sx#Ber5APPx|HjE(g%Xwchshik zlPG4j{ko0_FcEOw%C;N`UpHkQ`V#x>GslD85&qt*zE51v!4Bj*F)@u?rek`m9C}qn zX31MkB2S;3%+2teSja;%`f284m1T*}9xE04GHjv%1_GlB7k zh3*Ev`AGj!Sz@`k+!~Ml2bFf?1I6Xew#rnTgEb#_H)ZSTg^M1sv47?6W7%};=vR-a z;2AZGYZ-$W8>bcamd?(v?tGf(S?|KlhcA3WX0r(_yed=X8>8wxgK3-xwaO}4T6bSS z*)XtmYP&fDql5L4b+k_l!*y+I6CLW#uL%5%6gSCiiew^|1G3UnQ%7PmG&Nh`cD$Fg z5*|w5K-EKczxjsE|2YkEf7yxNm`)@=^}=tQ?yPZjcv12^#%Ig?X*ke0SJ}?pD(u#1 z(aPPKOe@KIx!Ui1edS|?PhvM5;Z6TdlZa&hlLPq-px1}8nmDWvlY4L%oO4@iWUzJ= z{B>Zp4-LUi6>N%2^~h$Jtapn{*X3*Xm6VChk&~E+)^-N~?{ieoCpB-g}*7^2B$Rt;o&WK3=M2O5dw}=h-FABa+c%8z5;qA zWX$Dp98=MWFHum~|H>0r+mD2py^J)q750pg(yx~o_>#PCb~!fwiSB&>Z@`z&5>;bS z#YEQYwgVsUOR9{pwedo7)hRN>*5u2sKNIKFv8_XNb*UxEqA$jWQb8cI=tOW_SqO_<4g<=-#?Mub z?DfDmUV_}^p99B&IdB}boPQ3S3OMl7pR)u^+TRGu#o;U#zo*Gf4qhl>xof z(oZmjIW2aZbUK~KWye|ETYPXhd0|;=ym|z}u8(1UAJl(@6jxoMu0DyaU(UU3(OT!r z`l!unT~haRAmupcV63CM@^RR1aw&wP>dqa+iL|l9dDDDqyR-6WBEu zZE={0ZPs47^>-Z#R6`=sbHGv#WhN`%F{$V0p7!XcVVmD?(`jQoaJyKsW6~(B$zN0n zIBRJqPu*$|2`^W<@?%Bj1&2os8cn2yaUoE9RzG)TD{M>?TEdv;h|o$8b`t9?+$|m; zJ+V6KR&dhhcx5JIbbINcYHP>x^6~)vQ=<6TNUcgwv{rUSd|X{`db;i`$sf-Py=T61 zI$ylVmPj$?>>Dhx50I!;c`hmWKmwPz$T>KcB~?Ox%Jx?TBdr&q;%CCM^Eg`L4s2tg z!LB%`zx$zuCCYdPKQjD+keI@ zN?gUmbN*-)B@S6S?a2;r90x`AfdMOnu0PhEC2?p7C^#t1w`1pQO@>*&jU*9D!0|cu zcbDEBPXQX)B82p36-sBxtAmT;b8|1j);lL!mZ{H%e+g29yyR5g-q%9u@4B zyt1*lk9;Mzs9Rx4hwIG>_BV%f!opq_bUoMFw;a#D>BGO@+`%`CvFPYT+5bzXoMC_P zp><9kTO>KDIB3gGTTW$eo%&9oP;tmpp7=qtC|(hp1pQlnKjmd~lYD}W77vZx))VfY z(IS%~k<*ViPx-r_OwicgJq|fp9k&noc^pIn9by*A`1+L>TO{Hr{yj+*LB0CrrcI01 zigXWO78?GDZjJUsJwbT zlst8~v|eftHS?SeTplI!<9z%=U|c46tVL;_vDoFJ^@Huf==;U0L{-5y8skE<jlik!hVL}4Y^*ROflPK6!{ z!TrE`8y_mt53s@f03|U=0K(C`r`?##CNdzn+d=itZ?HffQo_x)?Xa}*Y=CqUhkGeI z+MPB`pHolBv#tfjR!kK9Hn3GJajnA0T?|BRK&`nG6r7E;T?RC7YgW1AP|Z1`&M(}MT-_8FB8%ToZJTH+FR0J1L?P?Si!GNj$mXk|3y`%bk1PYAM4-L zbz}7WzVcp3yBi1f<_q|rsCheq7k>->-7DfSSf$;DQx>@+PN>02y{EG_@E>3%aUApnWpIHNQ5PqU&b=f6+i zlRh|!(cPITfPw-Y=t-*1k_Z`JA7Rb#bm@t%264Hh?U*FP@Un7zoi&l-c$4P zRsUS#@+TXxTxsVf;>8)(=yFIQUK|N5d9bDnc=s=tVwmZyN%-bO{)JIL5pWZp-}@w9 z)B8%%>d6-t?f1QqX6Z|e2xHs&d=Vy%G zZW$Jhg`TCQ*{ecwDb(s+U0{A?)!kr9oIl}{1m{n>_|64V!p^u1ol1j@hXW>gHlt5A z-hePJrv&4lz>kZ&0yr+m z6LR za$PNyUkpVtF1r1=|0$F}qD4!YBoHLw?1%$*-ZJ_A85|jWI|4g!22ZK(q9fga7>Z_F z50HjXJU_BA?s}@%lq^1*a`q;n12;(Zu02s4h%5JUjWdR`c>tUn8?d`ID6a;?@lv_(>~Ha6}Y%!NW#UqB<&^3@nME~gs31}n6sR`blpuX8n% zhJ7fYodAgEYB+6Z+o-AeIdBuxM450JJZlLS{BoSHkq3Cd?cG}`nE8vmOOb*EH78g= zyjAPDh>C@k4>dEj9!0Ml*cvQjl~1_1SJ33K?;1*JFh2?T^C>o^!puTv|Y?s zbarT%ljbjynE+wS1X&X6S|;VF=}B3`=l^#0jW3r5I}U`_8eu^-*Q5vdXaa)%cOwZzJ&mI@ZR{pEtoe0bu5eXs!AKW9O!FR zHTz{>23Ff^jeMmkNB}J<@OBk=j#bSD_fqmKqw(_fYVjBK8YKY$Tuxc5TK}OeF`%sV ztLe>X`iS)2lyH0c65Zy&2W&c21;!IYpE+EICCF~zx22;u0b6KDhvI zyA%%v`H`t;jrMnPhyAV|<~+&pH5)VAtI<$Up<4aJ5=a@MTVX^e;07Fs<%IiuRRgEg z`TY^f*y=drlY9KTtd?FPo^C5Mu1Dg|RZ|?X)$G%hB6fT(+h*PZqr)oq`vZ2XN>uLJ zv#@{WHLcpt4XwEMI4q0gFL(@ZWw;gnOSDbVjQ~Of>0n0226crPdFCWh+feN0DzP zIJDvU9qQglHRdUPbb{ zXK42A5$1ouY`-FP(?y+gfy4RdvBa_0TKdu%k8~&~&1Ff(Wr?MZLo?&ke6+7c%u=&` z`)F(t0(}7D+FjJRex> z(j0Ayy^g+`nT(3pl2sJ3+&}o?fA;4)in8K)4{t-b_b0pQS~(&%y>IbBN;09LD(%w5 z%z0n$0=rI&wCi=&9GVEbUITN8yC02i{CoJ@HBap*UXDv%T`e+*h(Jitt5t*qs2Xk zYn$gOPS2W|TDc9E=t_cWrST9h9Imx~SvoFYsAWan8&&hb_pZ{#MB_OIH||rqAFWSz zb&Eo^49~&?%GA)tVMY>Zc;+VkpCJrSJKXTd-ylcET@ws5y)~mqfJqezFlllReEC32 zLP8R2g(v(;*?)CobU(jWb$nWL{By3(kY*vkCdzS()b)AN~BW zWvsXP$}`Yz;-Y6KdzTjG=(Kt^z(Pi46y2gYG*q5H1ZPJMd3KyfjEWIw#{uS6=kNcx zfsBFd@ZPvh5X9->`D(H8Wrj~a1j$Pl+Ie+Zz{;0o>p`lCnv3_7_aPce1CW~H?Pz*M z29Df7i2*RTB~zXhU#nKon5BqC{)|G%^ZhMc_2YTp`)!e5?eqmov$rmM<63~k7LT>l z`L8k|AWqJyL0@+*-FdQG(Q`KUoSW^H{wgnin+moKc&kke@E~s^)D3s(E<6&UxDQY> zAs!TG#E*DyGTzqDXFK;y(#mNn_m90!DkmJijdSOYsmlj!O2wc^vTALB_R9P9XFZCE z&AffbT6TXf`&+gxZCI9^+5`|G)v4ZOq>N~7a`wih5d7LEKc+iarXKa>lvSV*h9GJ7 zxZt(F4Jc!HXMbDtyXH+ep6zpp)6xKsGQZQnf=;PhkqXG5)9%5}iwT102t;9!e=OO- ztPN#vf1joZQ`s`(=q%}f0zj*)TviLBYve;IUjV3ehpBP&kZ%1EZYICh^C=BS&WU^zx>PHqc&%cm?ZPC zVE*Vffd%}!&7=Ax895^M+ZOiJ)jfCS|7ApC-|ss(_5b=bm{qX5|0nX=HFVWveZT~uggF;3;BU;-|oa`2b1?mxIII(qgy@`gW=^SrbqZ~UMkUy zk7jPxW6pyrc3;?PSz!+>Hn6U?sQc_RS!n1b&>v8?lU=owT^buHM3hza>+w_kHd%fK zfd6?vM>>VA2i^DJmbF0=7AqxU8b3l>1|lqXCc6-OoarW?Ae|JJzw20 z?czde@X?9SE?42aYu3O6SZrHC(y)3mz z%!_T$=@Un6s1YTwvD_$}!nFTKd{xz9V7i$FQ%;BgMDuKhy~gXuvb+6}8?cl@#=`@b z5!ir(DDm;5v->2r)5Wy%34$L&2pC_j(E!sdoKX9{!uuB(Ai&`3@p-kzn}*YvjT;N+ zRVx}sc-lPulSOq4t(RPf>!ilY#cENXK1uYUNivhCgLY))XgAU_6ImIz*67!PR(hE^ zkBnJ{zJpTHOeoSsxgQ(#B91I4%tV>o7i~wL170lmCw9d;oDdSO+po*NT>VWxu$_MW z(-GUjDbC?u*)XJG7Z%J_qpqt;N@fo&K3Z4~KDQmO_^N2GfafN2V%$725ya)t&jO1U z)V5Y6H~tb^^)+A$9qTcI$e+);ZFs!f>a1*e?@-fvbx5-yo;#41v74_HpqM97$dG=L zE1n4v(i^~z>BUP9ROAs@zbW{T*U%&D$>v|Xi{%D*jJHk9GwW|T{(HKa46{oNo!h1Z z`-hI-1i$;3(nN9x!Eibw$Qu$8Q@K2Ne4-Q0#@w7uF6&IyO6hh`U^+plC8#5 z<+-`8A=w8r?{w$`K^YT%hi{e@XG_Gs%o z7L=DLJCxgb{HOAzh6*&ZrtK!Platp0`HZlgA63(|qSoYNC&`1wKg z1Vq`xv#sq>aMLT=46|vzH#r=Uzwe3iOa4$J7nw1`Ki$@dj?AB7M3=2Lstji2huc9u zD?e literal 0 HcmV?d00001 diff --git a/img/fpds-fields-vendor.png b/img/fpds-fields-vendor.png new file mode 100644 index 0000000000000000000000000000000000000000..f49bdcc448e36b9e9edcbd3c63acce2dbbab2ddd GIT binary patch literal 110368 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

MS%7162;Y7?AD(abK`nRiXA8hrZBN~jKu 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 literal 0 HcmV?d00001 From 67d3c1f584a947878b2169c6b1cf3f228b50563c Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 17:06:10 -0500 Subject: [PATCH 071/108] Update README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e670256d..64f282ea 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,10 @@ To make FPDS data more accesible to developers. This library helps users by doing the following: - Automatically handling pagination -- Converting XML and all associated attributes into JSON format +- Converting XML into a flat JSON structure + +This library is based on the FPDS ezSearch interface that can be found +[here](https://www.fpds.gov/ezsearch/search.do?indexName=awardfull&templateName=1.5.3&s=FPDS.GOV&q=). ## Prerequisites From dab8c308a040bfa272ac35eb3d682f56165c0cd2 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 18:16:17 -0500 Subject: [PATCH 072/108] Updating scraper now that we can get input boxes correctly --- src/fpds/scripts/scraper.py | 42 +++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 5d7f1009..0e6731c9 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -25,14 +25,8 @@ def update_fields_json(dropdown_fields: List[str]) -> None: # as of right now, we have no way to validate the pattern unless we go to the data dict new_options = [ - { - "description": "", - "name": field, - "quotes": False, - "regex": "", - } - for field in dropdown_fields - if field not in current_field_options + field for field in dropdown_fields + if field["name"] not in current_field_options ] config.extend(new_options) sorted_config = sorted(config, key=lambda field: field["name"]) @@ -66,6 +60,13 @@ def scrape_ezsearch() -> List[str]: add_button = advanced_search_div.find_element(By.CSS_SELECTOR, "input[title='Add']") add_button.click() + def _get_visible_div(driver): + divs = driver.find_elements(By.XPATH, "//div[starts-with(@id,'my0DivBox')]") + for div in divs: + if div.is_displayed(): + return div + return False + dropdowns = WebDriverWait(driver, 10).until( EC.presence_of_all_elements_located( (By.CSS_SELECTOR, "#advancedSearchdiv select") @@ -74,11 +75,26 @@ def scrape_ezsearch() -> List[str]: dropdown_fields = [] for dropdown in dropdowns: - element = dropdown.find_elements(By.TAG_NAME, "option") - for opt in element[1:]: # skip the first element since its the dropdown label - value = opt.get_attribute("value") - if value: - dropdown_fields.append(value) + elements = dropdown.find_elements(By.TAG_NAME, "option") + for element in elements[1:]: # skip the first element since its the dropdown label + try: + element.click() + div = WebDriverWait(driver, 10).until(_get_visible_div) + inputs = div.find_elements(By.XPATH, ".//input") + + dropdown_fields.append( + { + "name": element.get_attribute("value"), + "description": element.text, + "quotes": False if len(inputs) == 2 else True, + "regex": "", + + } + ) + + except: + print(f"Failed on element {element.text}") + continue return dropdown_fields From dea96dc942507f9d8475f6d090a5c68869b4fa79 Mon Sep 17 00:00:00 2001 From: dherincx92 <37759088+dherincx92@users.noreply.github.com> Date: Fri, 9 Jan 2026 23:16:38 +0000 Subject: [PATCH 073/108] patch: [skip ci] Run formatters --- src/fpds/scripts/scraper.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 0e6731c9..ff80e907 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -25,8 +25,7 @@ def update_fields_json(dropdown_fields: List[str]) -> None: # as of right now, we have no way to validate the pattern unless we go to the data dict new_options = [ - field for field in dropdown_fields - if field["name"] not in current_field_options + field for field in dropdown_fields if field["name"] not in current_field_options ] config.extend(new_options) sorted_config = sorted(config, key=lambda field: field["name"]) @@ -76,7 +75,9 @@ def _get_visible_div(driver): dropdown_fields = [] for dropdown in dropdowns: elements = dropdown.find_elements(By.TAG_NAME, "option") - for element in elements[1:]: # skip the first element since its the dropdown label + for element in elements[ + 1: + ]: # skip the first element since its the dropdown label try: element.click() div = WebDriverWait(driver, 10).until(_get_visible_div) @@ -88,7 +89,6 @@ def _get_visible_div(driver): "description": element.text, "quotes": False if len(inputs) == 2 else True, "regex": "", - } ) From 07d15c6d08199c72e35be57fb9fe310048539822 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 18:16:45 -0500 Subject: [PATCH 074/108] re run new fields workflow --- .github/workflows/pr-for-new-fields.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 967c6b1b..44a373a6 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -3,7 +3,7 @@ name: New fpds search fields PR on: schedule: - cron: "0 0 * * *" - # pull_request: # TODO: fully remove once done testing + pull_request: # TODO: fully remove once done testing workflow_dispatch: jobs: From 7c2ea2a5446157b5832d3877a3fa0712a5b7bac5 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 18:22:42 -0500 Subject: [PATCH 075/108] create separate formatters job due to clashing commits with matrix --- .github/workflows/test-package.yml | 40 ++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index a035bd95..ce7c84b0 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -6,7 +6,39 @@ on: - master jobs: - build-and-test: + run-formatters: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - uses: extractions/setup-just@v3 + with: + just-version: 1.46.0 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.13 + + - name: Install Project + run: just install + + - name: Run linters + run: just formatters + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "patch: [skip ci] Run formatters" + file_pattern: "*.py" + disable_globbing: true + + test-python-versions: runs-on: ubuntu-latest strategy: matrix: @@ -37,12 +69,6 @@ jobs: - name: Run linters run: just formatters - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: "patch: [skip ci] Run formatters" - file_pattern: "*.py" - disable_globbing: true - - name: Run Tests run: just test From a1fa638faaaf0d26b2950040c6657f5dcdb9e329 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 18:24:45 -0500 Subject: [PATCH 076/108] Add conditional to jst execute commit on 3.13 --- .github/workflows/test-package.yml | 41 ++++++------------------------ 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index ce7c84b0..7a2f9cf2 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -6,39 +6,7 @@ on: - master jobs: - run-formatters: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install uv - uses: astral-sh/setup-uv@v5 - - - uses: extractions/setup-just@v3 - with: - just-version: 1.46.0 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: 3.13 - - - name: Install Project - run: just install - - - name: Run linters - run: just formatters - - - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: "patch: [skip ci] Run formatters" - file_pattern: "*.py" - disable_globbing: true - - test-python-versions: + build-and-test: runs-on: ubuntu-latest strategy: matrix: @@ -69,6 +37,13 @@ jobs: - name: Run linters run: just formatters + - uses: stefanzweifel/git-auto-commit-action@v5 + if: ${{ matrix.python-version == '3.13' }} + with: + commit_message: "patch: [skip ci] Run formatters" + file_pattern: "*.py" + disable_globbing: true + - name: Run Tests run: just test From 142cf7746deacc5efd6eb2bd6cc734fcbaa90fd3 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 18:27:01 -0500 Subject: [PATCH 077/108] minor: updating README and removing duplicate text --- README.md | 75 ------------------------------------------------------- 1 file changed, 75 deletions(-) diff --git a/README.md b/README.md index 64f282ea..d3afc7e0 100644 --- a/README.md +++ b/README.md @@ -157,81 +157,6 @@ Run unit tests on your local environment: $ just test ``` -## Usage -For a list of valid search criteria parameters, consult FPDS documentation -found [here](https://www.fpds.gov/wiki/index.php/Atom_Feed_Usage). - -### CLI -Parameters will follow the `URL String` format shown in the link above, with the -following exceptions: - - + Colons (:) will be replaced by equal signs (=) - + Certain parameters enclose their value in quotations. `fpds` will -automatically determine if quotes are needed, so simply enclose your -entire criteria string in quotes. - - For example, `AGENCY_CODE:"3600"` should be used as `"AGENCY_CODE=3600"`. - -``` -$ uv run fpds parse "LAST_MOD_DATE=[2022/01/01, 2022/05/01]" "AGENCY_CODE=7504" -``` - -By default, data will be dumped into an `.fpds` folder at the user's -`$HOME` directory. If you wish to override this behavior, provide the `-o` -option. The directory will be created if it doesn't exist. - -``` -$ uv run fpds parse "LAST_MOD_DATE=[2022/01/01, 2022/05/01]" "AGENCY_CODE=7504" -o ~/.my-preferred-dir -``` - -As of v1.5.0, you can opt out of regex validation by setting the `-k` flag -to `False` — this is helpful in scenarios when either the regex pattern has -been altered by the ATOM feed or a new parameter name is supported, but not -yet added to the configuration in this library. - -_NOTE_: if you use `-k`, you will disable regex validation for all parameters, -which means you will be responsible for handling quoting yourself. This is -intentional. - -``` -$ uv run fpds parse -k "A_NEW_PARAM=a-new-value" -``` - -Let's say you ran the above command, but were unsure about the quoting strategy -and you wanted to run this using the CLI. As of v1.6.0, the `fields` command -provides a quick reference of available filtering fields. To print them out, run -the following command. Use the `--export` flag to export the full metadata for -fields (this is helpful if you wish to see quoting configuration and the regex validation pattern). - -``` -$ uv run fpds fields --export true -``` - -### Python - -Same request via python interpreter: -``` -import asyncio -from fpds import fpdsRequest - -request = fpdsRequest( - LAST_MOD_DATE="[2022/01/01, 2022/05/01]", - AGENCY_CODE="7504" -) - -# returns records as an async generator -gen = request.iter_data() - -# evaluating generator entries -records = [] -async for entry in gen: - records.append(entry) - -# or letting `data` method evaluate generator for you -records = asyncio.run(request.data()) -``` - - # Highlights Between v1.2.1 and v1.3.0, significant improvements were made with `asyncio`. Here are From 1fbafeafc6a65ab8cbebecfa7f45f6b0dfada1f3 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 18:28:41 -0500 Subject: [PATCH 078/108] inspired by Leon; using list instead of List --- src/fpds/core/parser.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index b1899051..2acbbab0 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -12,7 +12,7 @@ from asyncio import Semaphore from concurrent.futures import ProcessPoolExecutor from pathlib import Path -from typing import AsyncGenerator, List +from typing import AsyncGenerator from urllib import parse from urllib.request import urlopen from uuid import uuid4 @@ -110,7 +110,7 @@ def __init__( self.thread_count = thread_count self.max_chunk_size_mb = max_chunk_size_mb self.page = page - self.links: List[str] = [] + self.links: list[str] = [] if kwargs: self.kwargs = kwargs @@ -178,7 +178,7 @@ async def convert( subtree = FPDSSubTree(content=response.content) return subtree - async def fetch(self) -> List[FPDSSubTree]: + async def fetch(self) -> list[FPDSSubTree]: """Asynchronously parses all ATOM feed pages for current request.""" if not self.links: return [] @@ -225,7 +225,7 @@ def _create_progress() -> Progress: ) @staticmethod - def _jsonify(entry: FPDSSubTree) -> List[FPDS_ENTRY]: + def _jsonify(entry: FPDSSubTree) -> list[FPDS_ENTRY]: """Wrapper around `jsonify` method for avoiding pickle issue.""" return entry.jsonify() @@ -247,7 +247,7 @@ async def iter_data(self) -> AsyncGenerator[FPDS_ENTRY, None]: from concurrent.futures import as_completed num_processes = multiprocessing.cpu_count() - data = await self.fetch() # List[FPDSSubTree] + data = await self.fetch() # list[FPDSSubTree] with ProcessPoolExecutor(max_workers=num_processes) as pool: with self._create_progress() as progress: From cc53bba22d652bdbd99bfecdd46379c995bee89e Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 18:32:17 -0500 Subject: [PATCH 079/108] Change default chunk size to 10 MB instead of 100MB --- src/fpds/core/parser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index 2acbbab0..36c3ff3f 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -70,8 +70,8 @@ class FPDSRequest(FPDSMixin): Defaults to 10. The number of threads to send per search. max_chunk_size_mb: `int` - Defaults to 100. - The maximum size of each outputted data file (uncompressed). + Defaults to 10. + The maximum size of each outputted data file (uncompressed), in MB. page: `int | None` Defaults to `None`. The page of results to retrieve. @@ -101,7 +101,7 @@ def __init__( cli_run: bool = False, skip_regex_validation: bool = False, thread_count: int = 10, - max_chunk_size_mb: int = 100, + max_chunk_size_mb: int = 10, page: int | None = None, **kwargs: str, ) -> None: From ef7c9f9c188cf26bb0e7e830f41ef51745fc7db4 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 22:20:08 -0500 Subject: [PATCH 080/108] Add scraper job to retrieve data dict url and publish it in GHA --- .github/workflows/pr-for-new-fields.yml | 3 +- src/fpds/scripts/scraper.py | 76 +++++++++++++++++++------ 2 files changed, 62 insertions(+), 17 deletions(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 44a373a6..58edbae2 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -27,6 +27,7 @@ jobs: python-version: 3.13 - name: Scrape FPDS Site + id: scrape run: just scrape - name: Create Pull Request @@ -37,7 +38,7 @@ jobs: body: | Automated updates from FPDS ezSearch scraper. - Reference the following [data dictionary](https://www.fpds.gov/downloads/Version_1.5_specs/FPDS_DataDictionary_V1.5.pdf) + Reference the following [data dictionary](${{ steps.scrape.outputs.data_dict_url }}) for field descriptions and regex patterns. This is a bit of a pain since it is a manual process. diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index ff80e907..3fd56d88 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -1,12 +1,13 @@ """Scrapes fields from FPDS ezSearch page. author: derek663@gmail.com -last_updated: 2026-01-06 +last_updated: 2026-01-09 """ import json +import re +from packaging.version import Version from pathlib import Path -from typing import List from selenium import webdriver from selenium.webdriver.chrome.options import Options @@ -14,16 +15,27 @@ from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait -from fpds.config import FPDS_EZSEARCH_URL, FPDS_FIELDS_FILE_PATH +from fpds.config import ( + FPDS_EZSEARCH_URL, + FPDS_FIELDS_FILE_PATH, + FPDS_WORKSITE_URL, +) +SPEC_PATTERN = "V(.*?) Specifications" -def update_fields_json(dropdown_fields: List[str]) -> None: +def configure_driver(url: str) -> webdriver.Chrome: + options = Options() + options.add_argument("--headless") + driver = webdriver.Chrome(options=options) + driver.get(url=url) + return driver + + +def update_fields_json(dropdown_fields: list[str]) -> None: with Path(str(FPDS_FIELDS_FILE_PATH)).open(encoding="utf-8") as file: config = json.load(file) current_field_options = [field["name"] for field in config] - - # as of right now, we have no way to validate the pattern unless we go to the data dict new_options = [ field for field in dropdown_fields if field["name"] not in current_field_options ] @@ -34,17 +46,50 @@ def update_fields_json(dropdown_fields: List[str]) -> None: json.dump(sorted_config, file, indent=4) file.write("\n") +def scrape_latest_data_dictionary() -> str: + """Scrapes FPDS Worksite page for the latest data dictionary document.""" + driver = configure_driver(url=FPDS_WORKSITE_URL) + div = driver.find_elements( + By.XPATH, + "//div[h3[contains(normalize-space(.), 'Specifications')]]" + ) + + h3_tags = [] + for child in div: + h3 = child.find_element(By.TAG_NAME, "h3") + h3_tags.append(h3.text) -def scrape_ezsearch() -> List[str]: + prog = re.compile(SPEC_PATTERN) + versions = [ + prog.search(tag).group(1) for tag in h3_tags if prog.search(tag) + ] + highest = max(versions, key=lambda v: Version(v.strip())) + idx = h3_tags.index(f"V{highest} Specifications") + + tag = div[idx].find_element( + By.XPATH, + ".//a[contains(translate(normalize-space(.)," + "'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')," + "'data dictionary')]" + ) + data_dict_url = tag.get_attribute("href") + + import os + github_output = os.environ.get("GITHUB_OUTPUT") + if github_output: + with open(github_output, "a") as f: + f.write(f"data_dict_url={data_dict_url}\n") + return data_dict_url + + + +def scrape_ezsearch() -> list[str]: """Scrapes FPDS ezSearch field for Advanced Search Criteria dropdown values. These values represent valid parameter values for an instance of `:class:`fpdsRequest. """ - options = Options() - options.add_argument("--headless") - driver = webdriver.Chrome(options=options) - driver.get(FPDS_EZSEARCH_URL) + driver = configure_driver(url=FPDS_EZSEARCH_URL) search_criteria_button = driver.find_element( By.CSS_SELECTOR, @@ -75,9 +120,7 @@ def _get_visible_div(driver): dropdown_fields = [] for dropdown in dropdowns: elements = dropdown.find_elements(By.TAG_NAME, "option") - for element in elements[ - 1: - ]: # skip the first element since its the dropdown label + for element in elements[1:]: # first element is a label try: element.click() div = WebDriverWait(driver, 10).until(_get_visible_div) @@ -100,5 +143,6 @@ def _get_visible_div(driver): if __name__ == "__main__": - dropdown_fields = scrape_ezsearch() - update_fields_json(dropdown_fields=dropdown_fields) + scrape_latest_data_dictionary() + # dropdown_fields = scrape_ezsearch() + # update_fields_json(dropdown_fields=dropdown_fields) From 646bee965ed0325288ec046880165d7fbf2291ce Mon Sep 17 00:00:00 2001 From: dherincx92 <37759088+dherincx92@users.noreply.github.com> Date: Sat, 10 Jan 2026 03:20:31 +0000 Subject: [PATCH 081/108] patch: [skip ci] Run formatters --- src/fpds/scripts/scraper.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 3fd56d88..43248d40 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -6,9 +6,9 @@ import json import re -from packaging.version import Version from pathlib import Path +from packaging.version import Version from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By @@ -23,6 +23,7 @@ SPEC_PATTERN = "V(.*?) Specifications" + def configure_driver(url: str) -> webdriver.Chrome: options = Options() options.add_argument("--headless") @@ -46,12 +47,12 @@ def update_fields_json(dropdown_fields: list[str]) -> None: json.dump(sorted_config, file, indent=4) file.write("\n") + def scrape_latest_data_dictionary() -> str: """Scrapes FPDS Worksite page for the latest data dictionary document.""" driver = configure_driver(url=FPDS_WORKSITE_URL) div = driver.find_elements( - By.XPATH, - "//div[h3[contains(normalize-space(.), 'Specifications')]]" + By.XPATH, "//div[h3[contains(normalize-space(.), 'Specifications')]]" ) h3_tags = [] @@ -60,9 +61,7 @@ def scrape_latest_data_dictionary() -> str: h3_tags.append(h3.text) prog = re.compile(SPEC_PATTERN) - versions = [ - prog.search(tag).group(1) for tag in h3_tags if prog.search(tag) - ] + versions = [prog.search(tag).group(1) for tag in h3_tags if prog.search(tag)] highest = max(versions, key=lambda v: Version(v.strip())) idx = h3_tags.index(f"V{highest} Specifications") @@ -70,11 +69,12 @@ def scrape_latest_data_dictionary() -> str: By.XPATH, ".//a[contains(translate(normalize-space(.)," "'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')," - "'data dictionary')]" + "'data dictionary')]", ) data_dict_url = tag.get_attribute("href") import os + github_output = os.environ.get("GITHUB_OUTPUT") if github_output: with open(github_output, "a") as f: @@ -82,7 +82,6 @@ def scrape_latest_data_dictionary() -> str: return data_dict_url - def scrape_ezsearch() -> list[str]: """Scrapes FPDS ezSearch field for Advanced Search Criteria dropdown values. From 4855e69467e2c4179fb6e2204dfabfa4d7d7152e Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 22:26:35 -0500 Subject: [PATCH 082/108] Update config with new workspace URL --- src/fpds/config.py | 1 + src/fpds/scripts/scraper.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/fpds/config.py b/src/fpds/config.py index 45d1b978..7aaac79a 100644 --- a/src/fpds/config.py +++ b/src/fpds/config.py @@ -13,6 +13,7 @@ HOME = Path.home() CURRENT_DATE = datetime.now().strftime("%Y-%m-%d") FPDS_EZSEARCH_URL = "https://www.fpds.gov/ezsearch/fpdsportal?s=FPDS.GOV&templateName=1.5.3&indexName=awardfull&q=" +FPDS_WORKSITE_URL = "https://www.fpds.gov/fpdsng_cms/index.php/en/worksite.html" # FPDS-specific configurations FPDS_DATA_DIR = HOME / ".fpds" diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 3fd56d88..db1ab4a7 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -144,5 +144,5 @@ def _get_visible_div(driver): if __name__ == "__main__": scrape_latest_data_dictionary() - # dropdown_fields = scrape_ezsearch() - # update_fields_json(dropdown_fields=dropdown_fields) + dropdown_fields = scrape_ezsearch() + update_fields_json(dropdown_fields=dropdown_fields) From 2c0538768fb2a85dae2ddd3d8f833a1be16c4a95 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 22:54:16 -0500 Subject: [PATCH 083/108] Updating new fields workflow format and adding GHA output feed version to scraper script --- .github/workflows/pr-for-new-fields.yml | 10 ++++------ src/fpds/core/parser.py | 13 +------------ src/fpds/scripts/scraper.py | 3 ++- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 58edbae2..843982a1 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -38,13 +38,11 @@ jobs: body: | Automated updates from FPDS ezSearch scraper. - Reference the following [data dictionary](${{ steps.scrape.outputs.data_dict_url }}) - for field descriptions and regex patterns. This is a bit of a pain since - it is a manual process. + ✅ ATOM Feed Version: ${{ steps.scrape.outputs.feed_version }} + 📃 Data Dictionary [here](${{ steps.scrape.outputs.data_dict_url }}) - **Checklist** - - [ ] Updated field description - - [ ] Updated field regex + Before merging, please review the data dictionary file above and + generate the appropriate regex pattern for all new fields. branch: minor/new-ezsearch-fields base: master diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index 36c3ff3f..38b2ae2b 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -10,7 +10,7 @@ import multiprocessing import warnings from asyncio import Semaphore -from concurrent.futures import ProcessPoolExecutor +from concurrent.futures import ProcessPoolExecutor, as_completed from pathlib import Path from typing import AsyncGenerator from urllib import parse @@ -80,18 +80,9 @@ class FPDSRequest(FPDSMixin): Raises ------ - fpdsDuplicateParameterConfiguration: - Raised if duplicate configurations for a single parameter exist. - - fpdsInvalidParameter: - Raised if an invalid parameter is provided. - FPDSMaxPageLengthExceededError: Raised if user requests a page of results that doesn't exist. - fpdsMismatchedParameterRegexError: - Raised if parameter value does not match expected regex pattern. - FPDSMissingKeywordParameterError: Raised if no keyword argument(s) are provided. """ @@ -244,8 +235,6 @@ async def iter_data(self) -> AsyncGenerator[FPDS_ENTRY, None]: >>> async for entry in gen: >>> records.append(entry) """ - from concurrent.futures import as_completed - num_processes = multiprocessing.cpu_count() data = await self.fetch() # list[FPDSSubTree] diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index a33caf4b..b4157e85 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -5,6 +5,7 @@ """ import json +import os import re from pathlib import Path @@ -73,12 +74,12 @@ def scrape_latest_data_dictionary() -> str: ) data_dict_url = tag.get_attribute("href") - import os github_output = os.environ.get("GITHUB_OUTPUT") if github_output: with open(github_output, "a") as f: f.write(f"data_dict_url={data_dict_url}\n") + f.write(f"feed_version={highest}") return data_dict_url From 6d519484f83ca8126a7575170dc6ff4be7062173 Mon Sep 17 00:00:00 2001 From: dherincx92 <37759088+dherincx92@users.noreply.github.com> Date: Sat, 10 Jan 2026 03:54:36 +0000 Subject: [PATCH 084/108] patch: [skip ci] Run formatters --- src/fpds/scripts/scraper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index b4157e85..0c8449b1 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -74,7 +74,6 @@ def scrape_latest_data_dictionary() -> str: ) data_dict_url = tag.get_attribute("href") - github_output = os.environ.get("GITHUB_OUTPUT") if github_output: with open(github_output, "a") as f: From dd59b8bb198e29b57ef368c0a52c1022e50e084f Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 23:00:38 -0500 Subject: [PATCH 085/108] minor: update uv.lock and removing unstructured dependency --- src/fpds/constants/fields.json | 2 +- uv.lock | 26 ++++++++++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/fpds/constants/fields.json b/src/fpds/constants/fields.json index 556f4802..a8f41e0c 100644 --- a/src/fpds/constants/fields.json +++ b/src/fpds/constants/fields.json @@ -465,7 +465,7 @@ "description": "Initiative", "name": "SPENDING_CATEGORY", "quotes": true, - "regex": "American Recovery and Reinvestment Act" + "regex": "American Recovery and Reinvestment Act|Radical Transparency About Wasteful Spending" }, { "description": "Treasury Account Symbol Sub Account Code", diff --git a/uv.lock b/uv.lock index 48c3915c..af8e03b3 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,20 @@ version = 1 revision = 2 requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.13.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", +] [[package]] name = "anyio" @@ -65,7 +79,7 @@ name = "build" version = "0.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, + { name = "colorama", marker = "(os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "packaging" }, { name = "pep517" }, ] @@ -88,7 +102,7 @@ name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, + { name = "pycparser", marker = "implementation_name != 'PyPy' and sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ @@ -334,7 +348,7 @@ name = "cryptography" version = "46.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "cffi", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } wheels = [ @@ -1140,8 +1154,8 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography" }, - { name = "jeepney" }, + { name = "cryptography", marker = "sys_platform != 'darwin'" }, + { name = "jeepney", marker = "sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ @@ -1297,7 +1311,7 @@ version = "0.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, - { name = "cffi", marker = "implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "cffi", marker = "(implementation_name != 'pypy' and os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (implementation_name != 'pypy' and os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "idna" }, { name = "outcome" }, { name = "sniffio" }, From d72915dba216086c92c00d0494aa5448e48be21f Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 23:17:01 -0500 Subject: [PATCH 086/108] Add new github module --- .github/workflows/pr-for-new-fields.yml | 7 +++++++ src/fpds/scripts/scraper.py | 10 ++++++++-- src/fpds/utilities/github.py | 8 ++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/fpds/utilities/github.py diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 843982a1..d872ddf1 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -44,6 +44,13 @@ jobs: Before merging, please review the data dictionary file above and generate the appropriate regex pattern for all new fields. + **Failures** + The following fields were found in the ezSearch page, but their format + could not be determined. Manually inspect to determine its format. + If it already exists in `fields.json`, you can ignore this warning. + + {{ steps.scrape.outputs.grid }} + branch: minor/new-ezsearch-fields base: master labels: automated, feature diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 0c8449b1..0ee8bbe1 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -8,19 +8,21 @@ import os import re from pathlib import Path - from packaging.version import Version + from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait +from tabulate import tabulate from fpds.config import ( FPDS_EZSEARCH_URL, FPDS_FIELDS_FILE_PATH, FPDS_WORKSITE_URL, ) +from fpds.utilities.github import set_github_output SPEC_PATTERN = "V(.*?) Specifications" @@ -117,6 +119,7 @@ def _get_visible_div(driver): ) dropdown_fields = [] + failures = [] for dropdown in dropdowns: elements = dropdown.find_elements(By.TAG_NAME, "option") for element in elements[1:]: # first element is a label @@ -136,7 +139,10 @@ def _get_visible_div(driver): except: print(f"Failed on element {element.text}") - continue + failures.append([element.text]) + + grid = tabulate(failures, headers=["Name"], tablefmt="github") + set_github_output(grid=grid) return dropdown_fields diff --git a/src/fpds/utilities/github.py b/src/fpds/utilities/github.py new file mode 100644 index 00000000..5c5554f9 --- /dev/null +++ b/src/fpds/utilities/github.py @@ -0,0 +1,8 @@ +import os + +def set_github_output(**kwargs) -> None: + github_output = os.environ.get("GITHUB_OUTPUT") + if github_output: + with open(github_output, "a") as f: + for key, value in kwargs.items(): + f.write(f"{key}={value}\n") From abbc8815f6a3807cb479d25d144647f9d20fa544 Mon Sep 17 00:00:00 2001 From: dherincx92 <37759088+dherincx92@users.noreply.github.com> Date: Sat, 10 Jan 2026 04:17:26 +0000 Subject: [PATCH 087/108] patch: [skip ci] Run formatters --- src/fpds/scripts/scraper.py | 2 +- src/fpds/utilities/github.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 0ee8bbe1..d0fd1074 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -8,8 +8,8 @@ import os import re from pathlib import Path -from packaging.version import Version +from packaging.version import Version from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By diff --git a/src/fpds/utilities/github.py b/src/fpds/utilities/github.py index 5c5554f9..ecb07b5b 100644 --- a/src/fpds/utilities/github.py +++ b/src/fpds/utilities/github.py @@ -1,5 +1,6 @@ import os + def set_github_output(**kwargs) -> None: github_output = os.environ.get("GITHUB_OUTPUT") if github_output: From d177a6845ccf71ac4063bd51fc952e7267d7daa8 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Fri, 9 Jan 2026 23:57:56 -0500 Subject: [PATCH 088/108] Update set github output function to support multiline --- src/fpds/scripts/scraper.py | 7 +------ src/fpds/utilities/github.py | 5 ++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 0ee8bbe1..e368995b 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -5,7 +5,6 @@ """ import json -import os import re from pathlib import Path from packaging.version import Version @@ -76,11 +75,7 @@ def scrape_latest_data_dictionary() -> str: ) data_dict_url = tag.get_attribute("href") - github_output = os.environ.get("GITHUB_OUTPUT") - if github_output: - with open(github_output, "a") as f: - f.write(f"data_dict_url={data_dict_url}\n") - f.write(f"feed_version={highest}") + set_github_output(data_dict_url=data_dict_url, feed_version=highest) return data_dict_url diff --git a/src/fpds/utilities/github.py b/src/fpds/utilities/github.py index 5c5554f9..8dc855bb 100644 --- a/src/fpds/utilities/github.py +++ b/src/fpds/utilities/github.py @@ -5,4 +5,7 @@ def set_github_output(**kwargs) -> None: if github_output: with open(github_output, "a") as f: for key, value in kwargs.items(): - f.write(f"{key}={value}\n") + if "\n" in value: + f.write(f"{key}< Date: Sat, 10 Jan 2026 00:00:12 -0500 Subject: [PATCH 089/108] Add $ to ensure grid is rendered --- .github/workflows/pr-for-new-fields.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index d872ddf1..def52782 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -49,7 +49,7 @@ jobs: could not be determined. Manually inspect to determine its format. If it already exists in `fields.json`, you can ignore this warning. - {{ steps.scrape.outputs.grid }} + ${{ steps.scrape.outputs.grid }} branch: minor/new-ezsearch-fields base: master From ec12118106f4ee00ef5a11d22cbabd02c78de335 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 00:05:29 -0500 Subject: [PATCH 090/108] Update PR template --- .github/workflows/pr-for-new-fields.yml | 12 ++++++------ src/fpds/scripts/scraper.py | 8 ++++---- src/fpds/utilities/__init__.py | 2 ++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index def52782..1321cb80 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -41,13 +41,13 @@ jobs: ✅ ATOM Feed Version: ${{ steps.scrape.outputs.feed_version }} 📃 Data Dictionary [here](${{ steps.scrape.outputs.data_dict_url }}) - Before merging, please review the data dictionary file above and - generate the appropriate regex pattern for all new fields. + Before merging, please review the data dictionary file above and generate the appropriate + regex pattern for all new fields. - **Failures** - The following fields were found in the ezSearch page, but their format - could not be determined. Manually inspect to determine its format. - If it already exists in `fields.json`, you can ignore this warning. + 🔴 **Failures** 🔴 + ----------------- + The following fields were found in the ezSearch page, but their format could not be determined. Manually + inspect to determine its format. If it already exists in `fields.json`, you can ignore this warning. ${{ steps.scrape.outputs.grid }} diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 951e8bd9..4b99d691 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -7,8 +7,8 @@ import json import re from pathlib import Path - from packaging.version import Version + from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By @@ -21,7 +21,7 @@ FPDS_FIELDS_FILE_PATH, FPDS_WORKSITE_URL, ) -from fpds.utilities.github import set_github_output +from fpds.utilities import set_github_output SPEC_PATTERN = "V(.*?) Specifications" @@ -134,9 +134,9 @@ def _get_visible_div(driver): except: print(f"Failed on element {element.text}") - failures.append([element.text]) + failures.append([element.text, element.get_attribute("value")]) - grid = tabulate(failures, headers=["Name"], tablefmt="github") + grid = tabulate(failures, headers=["Name", "Description"], tablefmt="github") set_github_output(grid=grid) return dropdown_fields diff --git a/src/fpds/utilities/__init__.py b/src/fpds/utilities/__init__.py index 280d754f..549056d6 100644 --- a/src/fpds/utilities/__init__.py +++ b/src/fpds/utilities/__init__.py @@ -1,7 +1,9 @@ from .decorators import timeit +from .github import set_github_output from .params import validate_kwarg __all__ = [ + "set_github_output", "timeit", "validate_kwarg", ] From c9b5788ddb893a7bc22005f82bd149ea5c4f20dc Mon Sep 17 00:00:00 2001 From: dherincx92 <37759088+dherincx92@users.noreply.github.com> Date: Sat, 10 Jan 2026 05:05:53 +0000 Subject: [PATCH 091/108] patch: [skip ci] Run formatters --- src/fpds/scripts/scraper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 4b99d691..92f97b7c 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -7,8 +7,8 @@ import json import re from pathlib import Path -from packaging.version import Version +from packaging.version import Version from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By From d0d00f3865b3ec4a549a1f9183a5ab14fedc98d0 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 00:09:56 -0500 Subject: [PATCH 092/108] Expand text grid for new fields workflow --- .github/workflows/pr-for-new-fields.yml | 4 ++-- src/fpds/scripts/scraper.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 1321cb80..66dbf401 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -41,8 +41,8 @@ jobs: ✅ ATOM Feed Version: ${{ steps.scrape.outputs.feed_version }} 📃 Data Dictionary [here](${{ steps.scrape.outputs.data_dict_url }}) - Before merging, please review the data dictionary file above and generate the appropriate - regex pattern for all new fields. + Before merging, please review the data dictionary file above and generate the appropriate regex pattern + for all new fields. 🔴 **Failures** 🔴 ----------------- diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 4b99d691..e80429ca 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -134,7 +134,7 @@ def _get_visible_div(driver): except: print(f"Failed on element {element.text}") - failures.append([element.text, element.get_attribute("value")]) + failures.append([element.get_attribute("value"), element.text]) grid = tabulate(failures, headers=["Name", "Description"], tablefmt="github") set_github_output(grid=grid) From e0224fb334efb471569705beec29da77d638ccff Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 00:25:16 -0500 Subject: [PATCH 093/108] Add emoji for status --- src/fpds/scripts/scraper.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index b86bc741..3037a3cd 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -1,7 +1,7 @@ """Scrapes fields from FPDS ezSearch page. author: derek663@gmail.com -last_updated: 2026-01-09 +last_updated: 2026-01-10 """ import json @@ -18,6 +18,7 @@ from fpds.config import ( FPDS_EZSEARCH_URL, + FPDS_FIELDS_CONFIG, FPDS_FIELDS_FILE_PATH, FPDS_WORKSITE_URL, ) @@ -35,9 +36,8 @@ def configure_driver(url: str) -> webdriver.Chrome: def update_fields_json(dropdown_fields: list[str]) -> None: - with Path(str(FPDS_FIELDS_FILE_PATH)).open(encoding="utf-8") as file: - config = json.load(file) + config = FPDS_FIELDS_CONFIG current_field_options = [field["name"] for field in config] new_options = [ field for field in dropdown_fields if field["name"] not in current_field_options @@ -134,9 +134,15 @@ def _get_visible_div(driver): except: print(f"Failed on element {element.text}") - failures.append([element.get_attribute("value"), element.text]) - - grid = tabulate(failures, headers=["Name", "Description"], tablefmt="github") + match = next((f for f in FPDS_FIELDS_CONFIG if f["name"] == element.text), None) + status = "✅" if match else "❌" + failures.append([element.get_attribute("value"), element.text, status]) + + grid = tabulate( + tabular_data=failures, + headers=["Name", "Description", "Exists"], + tablefmt="github" + ) set_github_output(grid=grid) return dropdown_fields From 92b4d59d683096502c78a09250d3cba874c0fb09 Mon Sep 17 00:00:00 2001 From: dherincx92 <37759088+dherincx92@users.noreply.github.com> Date: Sat, 10 Jan 2026 05:25:41 +0000 Subject: [PATCH 094/108] patch: [skip ci] Run formatters --- src/fpds/scripts/scraper.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 3037a3cd..61e9c27f 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -36,7 +36,6 @@ def configure_driver(url: str) -> webdriver.Chrome: def update_fields_json(dropdown_fields: list[str]) -> None: - config = FPDS_FIELDS_CONFIG current_field_options = [field["name"] for field in config] new_options = [ @@ -134,14 +133,16 @@ def _get_visible_div(driver): except: print(f"Failed on element {element.text}") - match = next((f for f in FPDS_FIELDS_CONFIG if f["name"] == element.text), None) + match = next( + (f for f in FPDS_FIELDS_CONFIG if f["name"] == element.text), None + ) status = "✅" if match else "❌" failures.append([element.get_attribute("value"), element.text, status]) grid = tabulate( tabular_data=failures, headers=["Name", "Description", "Exists"], - tablefmt="github" + tablefmt="github", ) set_github_output(grid=grid) From daa4a974ff8a90c4db1c5bd2c9bf70ed315028cc Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 00:29:04 -0500 Subject: [PATCH 095/108] update name/description to make them reusable --- src/fpds/scripts/scraper.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 3037a3cd..e95820ce 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -118,6 +118,8 @@ def _get_visible_div(driver): for dropdown in dropdowns: elements = dropdown.find_elements(By.TAG_NAME, "option") for element in elements[1:]: # first element is a label + name = element.get_attribute("value") + description = element.text try: element.click() div = WebDriverWait(driver, 10).until(_get_visible_div) @@ -125,18 +127,17 @@ def _get_visible_div(driver): dropdown_fields.append( { - "name": element.get_attribute("value"), - "description": element.text, + "name": name, + "description": description, "quotes": False if len(inputs) == 2 else True, "regex": "", } ) except: - print(f"Failed on element {element.text}") - match = next((f for f in FPDS_FIELDS_CONFIG if f["name"] == element.text), None) + match = next((f for f in FPDS_FIELDS_CONFIG if f["name"] == name), None) status = "✅" if match else "❌" - failures.append([element.get_attribute("value"), element.text, status]) + failures.append([name, description, status]) grid = tabulate( tabular_data=failures, From 25b11430b7d03d4d4d03a01b96a97ace7e287bb8 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 00:32:59 -0500 Subject: [PATCH 096/108] minor: finalize PR format --- .github/workflows/pr-for-new-fields.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 66dbf401..45383bf1 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -46,8 +46,9 @@ jobs: 🔴 **Failures** 🔴 ----------------- - The following fields were found in the ezSearch page, but their format could not be determined. Manually - inspect to determine its format. If it already exists in `fields.json`, you can ignore this warning. + The following fields were found in the ezSearch page, but their format could not be determined. If the + field is marked as ❌, you will need to find relevant metadata in the data dictionary file linked above. + If the field is marked as ✅, you can ignore this warning. ${{ steps.scrape.outputs.grid }} From 6e13c67f93df26fe0a7fe5bd7e077ca1af55030c Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 00:36:13 -0500 Subject: [PATCH 097/108] remove unused parser test --- tests/test_parser.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/tests/test_parser.py b/tests/test_parser.py index ae9eaa13..093275d0 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -67,27 +67,6 @@ def test_no_pagination_links(self, mock_urlopen): req = FPDSRequest(**FPDS_REQUEST_PARAMS_DICT) self.assertEqual(asyncio.run(req.fetch()), []) - # @mock.patch("fpds.core.parser.urlopen") - # def test_convert(self, mock_urlopen): - # """Test that initial_request correctly returns the root XML tree from the initial request.""" - # mock_urlopen.return_value = MockHTTPResponse() - # req = FPDSRequest(**FPDS_REQUEST_PARAMS_DICT) - # mock_client = mock.AsyncMock() - # mock_client.get.return_value = MockHTTPResponse(content=FULL_RESPONSE_DATA_BYTES) - - # from asyncio import Semaphore - # semaphore = Semaphore(10) - # result = asyncio.run( - # req.convert( - # client=mock_client, - # link="https://example.com/fpds", - # semaphore=semaphore, - # ) - # ) - - # # Assertions - # self.assertIsInstance(result, fpdsSubTree) - @mock.patch("fpds.core.parser.urlopen") def test_request_link_count(self, mock_urlopen): """Test that generated number of links from initial request is correct.""" From 43a35e21ab571a3b9f52c2445ee309298a92785a Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 14:10:14 -0500 Subject: [PATCH 098/108] Update docstring for pattern in CLI command --- src/fpds/cli/fields.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/fpds/cli/fields.py b/src/fpds/cli/fields.py index 6fcc55a2..9df64dfa 100644 --- a/src/fpds/cli/fields.py +++ b/src/fpds/cli/fields.py @@ -1,7 +1,7 @@ """CLI command for listing available fitering fields. author: derek663@gmail.com -last_updated: 2025-12-14 +last_updated: 2026-01-10 """ import re @@ -24,14 +24,12 @@ def fields( typer.Option( "--pattern", "-p", - help=( - "String pattern to scan field name with. Will scan string and " - "attempt to find a match at the first location. Case-insensitive." - ), + help="Case-insesitive string pattern to search field name on." ), ] = None, width: Annotated[ - int, typer.Option("--width", "-w", help="Text wrap width for regex field.") + int, + typer.Option("--width", "-w", help="Text wrap width for regex field."), ] = TEXT_WRAP_WIDTH, ) -> None: """Displays list of available FPDS fields and their descriptions. @@ -47,7 +45,7 @@ def fields( """ data = [] - prog = re.compile(pattern, flags=re.I) if pattern else None + prog = re.compile(pattern, flags=re.IGNORECASE) if pattern else None def text_wrap(text: str, width: int = width) -> str: return textwrap.fill(text=text, width=width) @@ -59,7 +57,7 @@ def text_wrap(text: str, width: int = width) -> str: data.append( [ field["name"], - text_wrap(field["description"], width=width), + text_wrap(field["description"], width=20), text_wrap(field["regex"], width=width), ] ) From fa49453747dd490353382aa1436fe26552772db5 Mon Sep 17 00:00:00 2001 From: dherincx92 <37759088+dherincx92@users.noreply.github.com> Date: Sat, 10 Jan 2026 19:10:40 +0000 Subject: [PATCH 099/108] patch: [skip ci] Run formatters --- src/fpds/cli/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpds/cli/fields.py b/src/fpds/cli/fields.py index 9df64dfa..70a13c68 100644 --- a/src/fpds/cli/fields.py +++ b/src/fpds/cli/fields.py @@ -24,7 +24,7 @@ def fields( typer.Option( "--pattern", "-p", - help="Case-insesitive string pattern to search field name on." + help="Case-insesitive string pattern to search field name on.", ), ] = None, width: Annotated[ From adbcf83799c1c162f83892502d75b7c23a5fe86a Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 14:15:58 -0500 Subject: [PATCH 100/108] Add docstring for parse command --- src/fpds/cli/parse.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/fpds/cli/parse.py b/src/fpds/cli/parse.py index 56983913..4c515ba9 100644 --- a/src/fpds/cli/parse.py +++ b/src/fpds/cli/parse.py @@ -1,8 +1,7 @@ -""" -CLI command for retrieving FPDS federal contracts. +"""CLI command for retrieving FPDS federal contracts. author: derek663@gmail.com -last_updated: 2026-01-06 +last_updated: 2026-01-10 """ import asyncio @@ -33,15 +32,25 @@ def parse( ] = FPDS_DATA_DATE_DIR, params: list[str] = typer.Argument( ..., - help="Positional parameters (variadic, like nargs=-1)", + help="Positional parameters (variadic, like nargs=-1 in click)", ), ) -> None: - """Sends ATOM feed request to FPDS.""" + """Sends ATOM feed request to FPDS. + + \b + Usage: + $ uv run fpds parse [PARAMS] [OPTIONS] + + \b + Example(s): + $ uv run fpds parse "LAST_MOD_DATE=[2022/01/01, 2022/03/31]" + + """ output_dir_path: Path = Path(output_dir) output_dir_path.mkdir(parents=True, exist_ok=True) - split_params = [param.split("=") for param in params] # List[Tuple[str, str]] + split_params = [param.split("=") for param in params] # list[Tuple[str, str]] for _param in split_params: name, value = _param From cf5cfe495865aaa941ebca37947ad38bb3c72d11 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 14:32:32 -0500 Subject: [PATCH 101/108] Cleaning up some code and updating docstring for core FPDSRequest class --- src/fpds/core/parser.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/fpds/core/parser.py b/src/fpds/core/parser.py index 38b2ae2b..df776b9a 100644 --- a/src/fpds/core/parser.py +++ b/src/fpds/core/parser.py @@ -3,7 +3,7 @@ tree into JSON. author: derek663@gmail.com -last_updated: 2025-12-14 +last_updated: 2026-01-10 """ import asyncio @@ -47,13 +47,15 @@ class FPDSRequest(FPDSMixin): will validate argument names/values and raise an Exception if any error exists. - If you encounter new keyword parameters and/or an altered regex pattern, - use :param:`skip_regex_validation` to skip regex validation. Feel free - to submit an issue or open up a PR with new fields. + If you wish to opt-out of all regex validations, set `skip_regex_validation` + to `False`. Be aware that the provided regex validations are manually + created by parsing the FPDS Data Dictionary -- it is possible for these + patterns to have evolved over time. If you encounter any hiccups, please + submit an issue. Example: ------- - >>> request = fpdsRequest( + >>> request = FPDSRequest( >>> LAST_MOD_DATE="[2022/01/01, 2022/05/01]", >>> AGENCY_CODE="7504", >>> ) @@ -114,7 +116,7 @@ def __init__( if self.page: idx = self.page_index() - if idx is not None and self.links: + if idx and self.links: if self.page > self.page_count: raise FPDSMaxPageLengthExceededError(page_count=self.page_count) self.links = [links[idx]] @@ -128,9 +130,9 @@ def __init__( warnings.warn("Opting out of regex validation!") def __str__(self) -> str: # pragma: no cover - """String representation of `fpdsRequest`.""" + """String representation of `FPDSRequest`.""" kwargs_str = " ".join([f"{key}={value}" for key, value in self.kwargs.items()]) - return f"" + return f"" def __url__(self) -> str: # pragma: no cover """Custom magic method for request URL.""" @@ -190,7 +192,13 @@ async def convert_with_progress( task_id = progress.add_task("Fetching data...", total=len(self.links)) async with AsyncClient(timeout=None) as client: tasks = [ - convert_with_progress(client, link, semaphore, progress, task_id) + convert_with_progress( + client=client, + link=link, + semaphore=semaphore, + progress=progress, + task_id=task_id, + ) for link in self.links ] results = await asyncio.gather(*tasks) From 1fb5ffe2393bbaf6ae4b971aab296cb00bba2396 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 14:43:24 -0500 Subject: [PATCH 102/108] update docstrings in core/xml --- src/fpds/core/xml.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fpds/core/xml.py b/src/fpds/core/xml.py index 9b9382ea..5a574bfc 100644 --- a/src/fpds/core/xml.py +++ b/src/fpds/core/xml.py @@ -2,7 +2,7 @@ XML classes for parsing FPDS content. author: derek663@gmail.com -last_updated: 2025-07-25 +last_updated: 2026-01-10 """ import re @@ -195,9 +195,9 @@ class _ElementAttributes: ---------- prefix: `str` Prefix to append to attribute dictionary. This will ensure that - duplicate tags like `PIID` are distinguished in the data. + duplicate tags like `PIID` are distinguished in data. element: `xml.etree.ElementTree.Element` - An lxml Element type. + An `Element` type. """ def __init__(self, prefix: str, element: Element) -> None: From 4e0da0eeaa78b1aade1924a7e933e19c5ed524fe Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 15:52:29 -0500 Subject: [PATCH 103/108] update docstring for scraper --- src/fpds/scripts/scraper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpds/scripts/scraper.py b/src/fpds/scripts/scraper.py index 7547f2f0..77517598 100644 --- a/src/fpds/scripts/scraper.py +++ b/src/fpds/scripts/scraper.py @@ -1,4 +1,4 @@ -"""Scrapes fields from FPDS ezSearch page. +"""Scrapers for FPDS website. author: derek663@gmail.com last_updated: 2026-01-10 From 7ba5be5844fdc85601fc445098cf755efb9af277 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 15:53:45 -0500 Subject: [PATCH 104/108] add missing docstrings --- src/fpds/utilities/github.py | 6 ++++++ src/fpds/utilities/params.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/fpds/utilities/github.py b/src/fpds/utilities/github.py index 86775d35..da6c04ca 100644 --- a/src/fpds/utilities/github.py +++ b/src/fpds/utilities/github.py @@ -1,3 +1,9 @@ +"""Github utilities. + +author: derek663@gmail.com +last_updated: 2025-01-10 +""" + import os diff --git a/src/fpds/utilities/params.py b/src/fpds/utilities/params.py index 33ed431d..cd97ccbc 100644 --- a/src/fpds/utilities/params.py +++ b/src/fpds/utilities/params.py @@ -6,7 +6,7 @@ """ import re -from typing import Any, Dict, List, Optional, TypedDict, cast +from typing import Any, Dict, List, TypedDict, cast from fpds.config import FPDS_FIELDS_CONFIG as FIELDS from fpds.errors import ( From 13b917b63a1b957b933a32505372206b1249fae1 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 15:54:45 -0500 Subject: [PATCH 105/108] Cleaning up README --- README.md | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/README.md b/README.md index d3afc7e0..f178d2fe 100644 --- a/README.md +++ b/README.md @@ -134,29 +134,6 @@ async for entry in gen: records = asyncio.run(request.data()) ``` -### Local Development - -For linting and formatting, we use `ruff`. See `pyproject.toml` -for specific configuration. - -``` -$ make formatters -``` - -You can clean the clutter and unwanted noise from tools using: - -``` -$ make clean -``` - -### Testing - -Run unit tests on your local environment: - -``` -$ just test -``` - # Highlights Between v1.2.1 and v1.3.0, significant improvements were made with `asyncio`. Here are From 235b5ba73f3a26356a7917928c2e549a552a0c0f Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 15:59:18 -0500 Subject: [PATCH 106/108] Updating cron time --- .github/workflows/pr-for-new-fields.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 45383bf1..48d7c651 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -2,7 +2,7 @@ name: New fpds search fields PR on: schedule: - - cron: "0 0 * * *" + - cron: "0 5 * * *" pull_request: # TODO: fully remove once done testing workflow_dispatch: From e77e8a914878492ba7774b87db9ddf4e2ac23e27 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 16:04:37 -0500 Subject: [PATCH 107/108] Running scraper locally to ensure we get new fields from FPDS site directly --- src/fpds/constants/fields.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/fpds/constants/fields.json b/src/fpds/constants/fields.json index a8f41e0c..f1c3186d 100644 --- a/src/fpds/constants/fields.json +++ b/src/fpds/constants/fields.json @@ -335,6 +335,18 @@ "quotes": true, "regex": ".*" }, + { + "name": "NGC_PARTICIPATION_DESC", + "description": "Nontraditional Government Contractor Participation", + "quotes": true, + "regex": "" + }, + { + "name": "NON_GOVERNMENT_VALUE", + "description": "Non-Government Dollars", + "quotes": false, + "regex": "" + }, { "description": "Number of Offers Received", "name": "NUMBER_OF_OFFERS_RECEIVED", @@ -551,6 +563,12 @@ "quotes": true, "regex": ".*" }, + { + "name": "VENDOR_ADDRESS_COUNTRY_NAME", + "description": "Entity Country Name", + "quotes": true, + "regex": "" + }, { "description": "Entity Country Name", "name": "VENDOR_ADDRESS_COUNTRY_Name", From ffc1082e465e4d2d2280708962aeeeca89a34c65 Mon Sep 17 00:00:00 2001 From: Derek Herincx Date: Sat, 10 Jan 2026 16:05:01 -0500 Subject: [PATCH 108/108] Remove pull request trigger for new fields GHA workflow --- .github/workflows/pr-for-new-fields.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr-for-new-fields.yml b/.github/workflows/pr-for-new-fields.yml index 48d7c651..e58190e7 100644 --- a/.github/workflows/pr-for-new-fields.yml +++ b/.github/workflows/pr-for-new-fields.yml @@ -3,7 +3,6 @@ name: New fpds search fields PR on: schedule: - cron: "0 5 * * *" - pull_request: # TODO: fully remove once done testing workflow_dispatch: jobs: