diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 00000000..c19b712f --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,30 @@ +# .github/workflows/deploy.yml +name: Deploy +on: + push: + branches: + - main +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup PDM + uses: pdm-project/setup-pdm@v4 + with: + python-version: 3.12 + cache: true + + - name: Install dependencies + run: pdm install + + - name: Build docs + run: pdm docs + + - uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: docs/_build + branch: gh-pages + clean-exclude: pr-preview + force: false diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 3932ee8e..42792e40 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -19,7 +19,6 @@ jobs: uses: pdm-project/setup-pdm@v4 - name: Install dependencies run: | - pdm lock --dev pdm install - name: Run tests run: | @@ -40,48 +39,3 @@ jobs: - name: Check source code licenses run: | docker run --platform=linux/amd64 -v ${PWD}:/src ghcr.io/google/addlicense -v -check -l BSD-2-Clause -c "ChipFlow" -s=only -ignore **/__init__.py **/*.py - - build-docs: - needs: test - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up PDM - uses: pdm-project/setup-pdm@v4 - with: - python-version: 3.9 - - name: Install deps - run: pdm install - - name: Build docs - run: pdm run document - - name: Upload docs artifact - uses: actions/upload-artifact@v4 - with: - name: docs - path: docs/_build - - publish-docs: - needs: build-docs - if: ${{ github.repository == 'chipflow/chipflow-lib' }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Download docs artifact - uses: actions/download-artifact@v4 - with: - name: docs - path: docs/ - - name: Publish "latest" docs - if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/main' }} - uses: JamesIves/github-pages-deploy-action@releases/v4 - with: - repository-name: chipflow/chipflow.github.io - ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }} - branch: main - folder: docs/ - target-folder: chipflow-lib/latest/ - \ No newline at end of file diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/preview-docs.yml new file mode 100644 index 00000000..81dbb37f --- /dev/null +++ b/.github/workflows/preview-docs.yml @@ -0,0 +1,36 @@ +# .github/workflows/preview.yml +name: Deploy PR previews +concurrency: preview-${{ github.ref }} +on: + pull_request: + types: + - opened + - reopened + - synchronize + - closed +jobs: + deploy-preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup PDM + uses: pdm-project/setup-pdm@v4 + with: + python-version: 3.12 + cache: true + + - name: Install dependencies + run: pdm install + + - name: Build docs + run: pdm docs + if: github.event.action != 'closed' + + - uses: rossjrw/pr-preview-action@v1 + with: + source-dir: docs/_build + preview-branch: gh-pages + umbrella-dir: pr-preview + action: auto + pages-base-url: chipflow-lib.docs.chipflow-infra.com diff --git a/.gitignore b/.gitignore index d7b78032..aa32746c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,13 +2,14 @@ __pycache__/ *.egg-info /dist +/log* # pdm /.pdm-plugins /.pdm-python /.pdm-build /.venv -/pdm.lock +/.coverage # pytest /.pytest_cache diff --git a/README.md b/README.md index 27fe390f..f64e146b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ # ChipFlow library -WIP - +This is a small library to make working with the ChipFlow platform easy and straightforward. ## Installation and usage This library uses [PDM](https://pdm.fming.dev/) for dependency management, testing, building, and publishing. Install PDM first. Then run: ``` -pdm lock pdm install ``` diff --git a/chipflow_lib/__init__.py b/chipflow_lib/__init__.py index 21b1cc91..09adb0ba 100644 --- a/chipflow_lib/__init__.py +++ b/chipflow_lib/__init__.py @@ -1,4 +1,144 @@ -# SPDX-License-Identifier: BSD-2-Clause +import importlib.metadata +import jsonschema +import os +import sys +import tomli + +__version__ = importlib.metadata.version("chipflow_lib") + class ChipFlowError(Exception): pass + + +def _get_cls_by_reference(reference, context): + module_ref, _, class_ref = reference.partition(":") + try: + module_obj = importlib.import_module(module_ref) + except ModuleNotFoundError as e: + raise ChipFlowError(f"Module `{module_ref}` referenced by {context} is not found") from e + try: + return getattr(module_obj, class_ref) + except AttributeError as e: + raise ChipFlowError(f"Module `{module_ref}` referenced by {context} does not define " + f"`{class_ref}`") from e + + +def _ensure_chipflow_root(): + if "CHIPFLOW_ROOT" not in os.environ: + os.environ["CHIPFLOW_ROOT"] = os.getcwd() + if os.environ["CHIPFLOW_ROOT"] not in sys.path: + sys.path.append(os.environ["CHIPFLOW_ROOT"]) + return os.environ["CHIPFLOW_ROOT"] + + +# TODO: convert to pydantic, one truth of source for the schema +config_schema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://chipflow.io/meta/chipflow.toml.schema.json", + "title": "chipflow.toml", + "type": "object", + "required": [ + "chipflow" + ], + "properties": { + "chipflow": { + "type": "object", + "required": [ + "steps", + "silicon" + ], + "additionalProperties": False, + "properties": { + "project_name": { + "type": "string", + }, + "top": { + "type": "object", + }, + "steps": { + "type": "object", + }, + "clocks": { + "type": "object", + "patternPropertues": { + ".+": {"type": "string"} + }, + }, + "resets": { + "type": "object", + "patternPropertues": { + ".+": {"type": "string"} + }, + }, + "silicon": { + "type": "object", + "required": [ + "process", + "package", + ], + "additionalProperties": False, + "properties": { + "process": { + "enum": ["sky130", "gf180", "customer1", "gf130bcd", "ihp_sg13g2"] + }, + "package": { + "enum": ["caravel", "cf20", "pga144"] + }, + "pads": {"$ref": "#/$defs/pin"}, + "power": {"$ref": "#/$defs/pin"}, + "debug": { + "type": "object", + "properties": { + "heartbeat": {"type": "boolean"} + } + } + }, + }, + }, + }, + }, + "$defs": { + "pin": { + "type": "object", + "additionalProperties": False, + "minProperties": 1, + "patternProperties": { + ".+": { + "type": "object", + "required": [ + "type", + "loc", + ], + "additionalProperties": False, + "properties": { + "type": { + "enum": ["io", "i", "o", "oe", "clock", "reset", "power", "ground"] + }, + "loc": { + "type": "string", + "pattern": "^[NSWE]?[0-9]+$" + }, + } + } + } + } + } +} + + +def _parse_config(): + chipflow_root = _ensure_chipflow_root() + config_file = f"{chipflow_root}/chipflow.toml" + return _parse_config_file(config_file) + + +def _parse_config_file(config_file): + with open(config_file, "rb") as f: + config_dict = tomli.load(f) + + try: + jsonschema.validate(config_dict, config_schema) + return config_dict + except jsonschema.ValidationError as e: + raise ChipFlowError(f"Syntax error in `chipflow.toml` at `{'.'.join(e.path)}`: {e.message}") diff --git a/chipflow_lib/cli.py b/chipflow_lib/cli.py index bf189416..193c4a92 100644 --- a/chipflow_lib/cli.py +++ b/chipflow_lib/cli.py @@ -1,173 +1,83 @@ # SPDX-License-Identifier: BSD-2-Clause - -import os -import sys -import inspect -import importlib import argparse -import tomli -import jsonschema - -from . import ChipFlowError - - -def _get_cls_by_reference(reference, context): - module_ref, _, class_ref = reference.partition(":") - try: - module_obj = importlib.import_module(module_ref) - except ModuleNotFoundError as e: - raise ChipFlowError(f"Module `{module_ref}` referenced by {context} is not found") - try: - return getattr(module_obj, class_ref) - except AttributeError as e: - raise ChipFlowError(f"Module `{module_ref}` referenced by {context} does not define " - f"`{class_ref}`") from None - - -def _ensure_chipflow_root(): - if "CHIPFLOW_ROOT" not in os.environ: - os.environ["CHIPFLOW_ROOT"] = os.getcwd() - if os.environ["CHIPFLOW_ROOT"] not in sys.path: - sys.path.append(os.environ["CHIPFLOW_ROOT"]) - return os.environ["CHIPFLOW_ROOT"] +import inspect +import sys +import traceback +import logging +from pprint import pformat -config_schema = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://chipflow.io/meta/chipflow.toml.schema.json", - "title": "chipflow.toml", - "type": "object", - "required": [ - "chipflow" - ], - "properties": { - "chipflow": { - "type": "object", - "required": [ - "steps", - "silicon" - ], - "additionalProperties": False, - "properties": { - "project_id": { - "type": "integer", - }, - "steps": { - "type": "object", - }, - "silicon": { - "type": "object", - "required": [ - "process", - "pad_ring", - "pads", - ], - "additionalProperties": False, - "properties": { - "process": { - "enum": ["sky130", "gf180", "customer1", "gf130bcd", "ihp_sg13g2"] - }, - "pad_ring": { - "enum": ["caravel", "cf20", "pga144"] - }, - "pads": { - "type": "object", - "additionalProperties": False, - "minProperties": 1, - "patternProperties": { - ".+": { - "type": "object", - "required": [ - "type", - "loc", - ], - "additionalProperties": False, - "properties": { - "type": { - "enum": ["io", "i", "o", "oe", "clk"] - }, - "loc": { - "type": "string", - "pattern": "^[NSWE]?[0-9]+$" - }, - } - } - } - }, - "power": { - "type": "object", - "additionalProperties": False, - "patternProperties": { - ".+": { - "type": "object", - "required": [ - "loc", - ], - "additionalProperties": False, - "properties": { - "loc": { - "type": "string", - "pattern": "^[NSWE]?[0-9]+$" - }, - } - } - } - }, - } - }, - }, - } - } -} +from . import ( + ChipFlowError, + _get_cls_by_reference, + _parse_config, +) +from .pin_lock import PinCommand -def _parse_config(): - chipflow_root = _ensure_chipflow_root() - config_file = f"{chipflow_root}/chipflow.toml" - return _parse_config_file(config_file) +logging.basicConfig(stream=sys.stdout, level=logging.WARNING) -def _parse_config_file(config_file): - with open(config_file, "rb") as f: - config_dict = tomli.load(f) - - try: - jsonschema.validate(config_dict, config_schema) - return config_dict - except jsonschema.ValidationError as e: - raise ChipFlowError(f"Syntax error in `chipflow.toml` at `{'.'.join(e.path)}`: {e.message}") +class UnexpectedError(ChipFlowError): + pass def run(argv=sys.argv[1:]): config = _parse_config() - steps = {} + commands = {} + commands["pin"] = PinCommand(config) + for step_name, step_reference in config["chipflow"]["steps"].items(): step_cls = _get_cls_by_reference(step_reference, context=f"step `{step_name}`") try: - steps[step_name] = step_cls(config) + commands[step_name] = step_cls(config) except Exception: raise ChipFlowError(f"Encountered error while initializing step `{step_name}` " - f"using `{step_reference}`") - - parser = argparse.ArgumentParser() - step_argument = parser.add_subparsers(dest="step", required=True) - for step_name, step_cls in steps.items(): - step_subparser = step_argument.add_parser(step_name, help=inspect.getdoc(step_cls)) + f"using `{step_reference}`") + + parser = argparse.ArgumentParser( + prog="chipflow", + description="Command line tool for interacting with the ChipFlow platform") + + parser.add_argument( + "--verbose", "-v", + dest="log_level", + action="append_const", + const=10, + help="increase verbosity of messages; can be supplied multiple times to increase verbosity" + ) + + command_argument = parser.add_subparsers(dest="command", required=True) + for command_name, command in commands.items(): + command_subparser = command_argument.add_parser(command_name, help=inspect.getdoc(command)) try: - step_cls.build_cli_parser(step_subparser) + command.build_cli_parser(command_subparser) except Exception: raise ChipFlowError(f"Encountered error while building CLI argument parser for " - f"step `{step_name}`") + f"step `{command_name}`") + # each verbose flag increases versbosity (e.g. -v -v, -vv, --verbose --verbose) + # cute trick using append_const and summing args = parser.parse_args(argv) - try: - steps[args.step].run_cli(args) - except ChipFlowError: - raise - except Exception: - raise ChipFlowError(f"Encountered error while running CLI for step `{args.step}`") - + if args.log_level: + log_level = max(logging.DEBUG, logging.WARNING - sum(args.log_level)) + logging.getLogger().setLevel(log_level) -if __name__ == '__main__': - run() + try: + try: + commands[args.command].run_cli(args) + except ChipFlowError: + raise + except Exception as e: + # convert to ChipFlowError so all handling is same. + raise UnexpectedError( + f"Unexpected error, please report to ChipFlow:\n" + f"args =\n{pformat(args)}\n" + f"traceback =\n{''.join(traceback.format_exception(e))}" + ) from e + except ChipFlowError as e: + cmd = args.command + if hasattr(args, "action"): + cmd += f" {args.action}" + print(f"Error while executing `{cmd}`: {e}") diff --git a/chipflow_lib/errors.py b/chipflow_lib/errors.py new file mode 100644 index 00000000..6dcb7917 --- /dev/null +++ b/chipflow_lib/errors.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: BSD-2-Clause +class ChipFlowError(Exception): + pass diff --git a/chipflow_lib/pin_lock.py b/chipflow_lib/pin_lock.py new file mode 100644 index 00000000..934f7ad4 --- /dev/null +++ b/chipflow_lib/pin_lock.py @@ -0,0 +1,169 @@ +# SPDX-License-Identifier: BSD-2-Clause +import inspect +import logging + +from pprint import pformat +from pathlib import Path +from typing import Any, List, Dict, Tuple + +from chipflow_lib import _parse_config, ChipFlowError +from chipflow_lib.platforms import PACKAGE_DEFINITIONS, PIN_ANNOTATION_SCHEMA, top_interfaces +from chipflow_lib.platforms.utils import LockFile, Package, PortMap, Port + +# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +logger = logging.getLogger(__name__) + + +def count_member_pins(name: str, member: Dict[str, Any]) -> int: + "Counts the pins from amaranth metadata" + logger.debug( + f"count_pins {name} {member['type']} " + f"{member['annotations'] if 'annotations' in member else 'no annotations'}" + ) + if member['type'] == 'interface' and 'annotations' in member \ + and PIN_ANNOTATION_SCHEMA in member['annotations']: + return member['annotations'][PIN_ANNOTATION_SCHEMA]['width'] + elif member['type'] == 'interface': + width = 0 + for n, v in member['members'].items(): + width += count_member_pins('_'.join([name, n]), v) + return width + elif member['type'] == 'port': + return member['width'] + + +def allocate_pins(name: str, member: Dict[str, Any], pins: List[str]) -> Tuple[Dict[str, Port], List[str]]: + "Allocate pins based of Amaranth member metadata" + + pin_map = {} + logger.debug(f"allocate_pins: name={name}, pins={pins}") + logger.debug(f"member={pformat(member)}") + + if member['type'] == 'interface' and 'annotations' in member \ + and PIN_ANNOTATION_SCHEMA in member['annotations']: + logger.debug("matched PinSignature {sig}") + name = name + sig = member['annotations'][PIN_ANNOTATION_SCHEMA] + width = sig['width'] + pin_map[name] = {'pins': pins[0:width], + 'direction': sig['direction'], + 'type': 'io'} + logger.debug(f"added '{name}':{pin_map[name]} to pin_map") + return pin_map, pins[width:] + elif member['type'] == 'interface': + for k, v in member['members'].items(): + n = '_'.join([name, k]) + _map, pins = allocate_pins(n, v, pins) + pin_map |= _map + logger.debug(f"{pin_map},{_map}") + return pin_map, pins + elif member['type'] == 'port': + logger.warning(f"Port '{name}' has no PinSignature, pin allocation likely to be wrong") + name = name + width = member['width'] + pin_map[name] = {'pins': pins[0:width], + 'direction': member['dir'], + 'type': 'io'} + logger.debug(f"added '{name}':{pin_map[name]} to pin_map") + return pin_map, pins[width:] + else: + logging.debug(f"Shouldnt get here. member = {member}") + assert False + + +def lock_pins() -> None: + config = _parse_config() + used_pins = set() + oldlock = None + + lockfile = Path('pins.lock') + if lockfile.exists(): + json_string = lockfile.read_text() + oldlock = LockFile.model_validate_json(json_string) + + print(f"Locking pins: {'using pins.lock' if lockfile.exists() else ''}") + package_name = config["chipflow"]["silicon"]["package"] + + if package_name not in PACKAGE_DEFINITIONS: + logger.debug(f"Package '{package_name} is unknown") + package_type = PACKAGE_DEFINITIONS[package_name] + + package = Package(package_type=package_type) + for d in ("pads", "power"): + logger.debug(f"Checking [chipflow.silicon.{d}]:") + _map = {} + for k, v in config["chipflow"]["silicon"][d].items(): + pin = str(v['loc']) + used_pins.add(pin) + port = oldlock.package.check_pad(k, v) if oldlock else None + if port and port.pins != [pin]: + raise ChipFlowError( + f"chipflow.toml conflicts with pins.lock: " + f"{k} had pin {port.pins}, now {[pin]}." + ) + package.add_pad(k, v) + + logger.debug(f'Pins in use: {package_type.sortpins(used_pins)}') + + unallocated = package_type.pins - used_pins + + logger.debug(f"unallocated pins = {package_type.sortpins(unallocated)}") + + _, interfaces = top_interfaces(config) + + logger.debug(f"All interfaces:\n{pformat(interfaces)}") + + port_map = PortMap({}) + # we try to keep pins together for each interface + for component, iface in interfaces.items(): + for k, v in iface['interface']['members'].items(): + logger.debug(f"Interface {component}.{k}:") + logger.debug(pformat(v)) + width = count_member_pins(k, v) + logger.debug(f" {k}: total {width} pins") + old_ports = oldlock.port_map.get_ports(component, k) if oldlock else None + if old_ports: + logger.debug(f" {component}.{k} found in pins.lock, reusing") + logger.debug(pformat(old_ports)) + old_width = sum([len(p.pins) for p in old_ports.values()]) + if old_width != width: + raise ChipFlowError( + f"top level interface has changed size. " + f"Old size = {old_width}, new size = {width}" + ) + port_map.add_ports(component, k, old_ports) + else: + pins = package_type.allocate(unallocated, width) + if len(pins) == 0: + raise ChipFlowError("No pins were allocated by {package}") + logger.debug(f"allocated range: {pins}") + unallocated = unallocated - set(pins) + _map, _ = allocate_pins(k, v, pins) + port_map.add_ports(component, k, _map) + + newlock = LockFile(package=package, port_map=port_map, metadata=interfaces) + + with open('pins.lock', 'w') as f: + f.write(newlock.model_dump_json(indent=2, serialize_as_any=True)) + + +class PinCommand: + def __init__(self, config): + self.config = config + + def build_cli_parser(self, parser): + action_argument = parser.add_subparsers(dest="action") + action_argument.add_parser( + "lock", help=inspect.getdoc(self.lock).splitlines()[0]) + + def run_cli(self, args): + logger.debug(f"command {args}") + if args.action == "lock": + self.lock() + + def lock(self): + """Lock the pin map for the design. + + Will attempt to reuse previous pin positions. + """ + lock_pins() diff --git a/chipflow_lib/platforms/__init__.py b/chipflow_lib/platforms/__init__.py index e69de29b..9cf52f75 100644 --- a/chipflow_lib/platforms/__init__.py +++ b/chipflow_lib/platforms/__init__.py @@ -0,0 +1,3 @@ +from .silicon import * +from .sim import * +from .utils import * diff --git a/chipflow_lib/platforms/silicon.py b/chipflow_lib/platforms/silicon.py index 32eb6c7a..1a32624f 100644 --- a/chipflow_lib/platforms/silicon.py +++ b/chipflow_lib/platforms/silicon.py @@ -1,65 +1,114 @@ # SPDX-License-Identifier: BSD-2-Clause - +import logging import os import subprocess -from amaranth import * -from amaranth.lib import io +from dataclasses import dataclass + +from amaranth import Module, Signal, Cat, ClockDomain, ClockSignal, ResetSignal + +from amaranth.lib import wiring, io +from amaranth.lib.cdc import FFSynchronizer +from amaranth.lib.wiring import Component, In from amaranth.back import rtlil from amaranth.hdl import Fragment from amaranth.hdl._ir import PortDirection from .. import ChipFlowError - +from .utils import load_pinlock, Port __all__ = ["SiliconPlatformPort", "SiliconPlatform"] +logger = logging.getLogger(__name__) + + +def make_hashable(cls): + def __hash__(self): + return hash(id(self)) + + def __eq__(self, obj): + return id(self) == id(obj) + + cls.__hash__ = __hash__ + cls.__eq__ = __eq__ + return cls + + +HeartbeatSignature = wiring.Signature({"heartbeat_i": In(1)}) + + +@make_hashable +@dataclass +class Heartbeat(Component): + clock_domain: str = "sync" + counter_size: int = 23 + name: str = "heartbeat" + + def __init__(self, ports): + super().__init__(HeartbeatSignature) + self.ports = ports + + def elaborate(self, platform): + m = Module() + # Heartbeat LED (to confirm clock/reset alive) + heartbeat_ctr = Signal(self.counter_size) + getattr(m.d, self.clock_domain).__iadd__(heartbeat_ctr.eq(heartbeat_ctr + 1)) + + heartbeat_buffer = io.Buffer("o", self.ports.heartbeat) + m.submodules.heartbeat_buffer = heartbeat_buffer + m.d.comb += heartbeat_buffer.o.eq(heartbeat_ctr[-1]) + return m + class SiliconPlatformPort(io.PortLike): - def __init__(self, name, direction, width, *, invert=False): - if not isinstance(name, str): - raise TypeError(f"Name must be a string, not {name!r}") - if not (isinstance(width, int) and width >= 0): - raise TypeError(f"Width must be a non-negative integer, not {width!r}") - if not isinstance(invert, bool): - raise TypeError(f"'invert' must be a bool, not {invert!r}") - - self._direction = io.Direction(direction) + def __init__(self, + component: str, + name: str, + port: Port, + *, + invert: bool = False): + self._direction = io.Direction(port.direction) self._invert = invert - self._i = self._o = self._oe = None + self._i = self._o = self._oe = Signal(1) if self._direction in (io.Direction.Input, io.Direction.Bidir): - self._i = Signal(width, name=f"{name}__i") + self._i = Signal(port.width, name=f"{component}_{name}__i") if self._direction in (io.Direction.Output, io.Direction.Bidir): - self._o = Signal(width, name=f"{name}__o") - self._oe = Signal(width, name=f"{name}__oe") + self._o = Signal(port.width, name=f"{component}_{name}__o") + self._oe = Signal(1, name=f"{component}_{name}__oe") + self._pins = port.pins + logger.debug(f"Created SiliconPlatformPort {name}, width={len(port.pins)},dir{self._direction}") @property def i(self): if self._i is None: raise AttributeError("SiliconPlatformPort with output direction does not have an " - "input signal") + "input signal") return self._i @property def o(self): if self._o is None: raise AttributeError("SiliconPlatformPort with input direction does not have an " - "output signal") + "output signal") return self._o @property def oe(self): if self._oe is None: raise AttributeError("SiliconPlatformPort with input direction does not have an " - "output enable signal") + "output enable signal") return self._oe @property def direction(self): return self._direction + @property + def pins(self): + return self._pins + @property def invert(self): return self._invert @@ -73,7 +122,7 @@ def __len__(self): if self._direction is io.Direction.Bidir: assert len(self._i) == len(self._o) == len(self._oe) return len(self._i) - assert False # :nocov: + assert False # :nocov: def __getitem__(self, key): result = object.__new__(type(self)) @@ -103,6 +152,11 @@ def __add__(self, other): result._direction = direction return result + def __repr__(self): + return (f"SiliconPlatformPort(direction={repr(self._direction)}, width={len(self)}, " + f"i={repr(self._i)}, o={repr(self._o)}, oe={repr(self._oe)}, " + f"invert={repr(self._invert)})") + class IOBuffer(io.Buffer): def elaborate(self, platform): @@ -160,39 +214,58 @@ def elaborate(self, platform): class SiliconPlatform: - def __init__(self, pads): - self._pads = pads + def __init__(self, config): + self._config = config self._ports = {} self._files = {} - def request(self, name): + def instantiate_ports(self, m: Module): + if hasattr(self, "pinlock"): + return + + pinlock = load_pinlock() + for component, iface in pinlock.port_map.items(): + for k, v in iface.items(): + for name, port in v.items(): + self._ports[name] = SiliconPlatformPort(component, name, port) + + for clock, name in self._config["chipflow"]["clocks"].items(): + if name not in pinlock.package.clocks: + raise ChipFlowError("Unable to find clock {name} in pinlock") + + port_data = pinlock.package.clocks[name] + port = SiliconPlatformPort(component, name, port_data, invert=True) + self._ports[name] = port + if clock == 'default': + clock = 'sync' + setattr(m.domains, clock, ClockDomain(name=clock)) + clk_buffer = io.Buffer("i", port) + setattr(m.submodules, "clk_buffer_" + clock, clk_buffer) + m.d.comb += ClockSignal().eq(clk_buffer.i) + + for reset, name in self._config["chipflow"]["resets"].items(): + port_data = pinlock.package.resets[name] + port = SiliconPlatformPort(component, name, port_data) + self._ports[name] = port + rst_buffer = io.Buffer("i", port) + setattr(m.submodules, reset, rst_buffer) + setattr(m.submodules, reset + "_sync", FFSynchronizer(rst_buffer.i, ResetSignal())) + + self.pinlock = pinlock + + def request(self, name=None, **kwargs): if "$" in name: raise NameError(f"Reserved character `$` used in pad name `{name}`") - if name not in self._pads: - raise NameError(f"Pad `{name}` is not defined in chipflow.toml") - if name in self._ports: - raise NameError(f"Pad `{name}` has already been requested") - - pad_type = self._pads[name]["type"] - # `clk` is used for clock tree synthesis, but treated as `i` in frontend - if pad_type in ("i", "clk"): - direction = io.Direction.Input - elif pad_type in ("o", "oe"): - direction = io.Direction.Output - elif pad_type == "io": - direction = io.Direction.Bidir - else: - assert False - - self._ports[name] = port = SiliconPlatformPort(name, direction, 1) - return port + if name not in self._ports: + raise NameError(f"Pad `{name}` is not present in the pin lock") + return self._ports[name] def get_io_buffer(self, buffer): if isinstance(buffer, io.Buffer): result = IOBuffer(buffer.direction, buffer.port) elif isinstance(buffer, io.FFBuffer): result = FFBuffer(buffer.direction, buffer.port, - i_domain=buffer.i_domain, o_domain=buffer.o_domain) + i_domain=buffer.i_domain, o_domain=buffer.o_domain) else: raise TypeError(f"Unsupported buffer type {buffer!r}") @@ -215,7 +288,7 @@ def add_file(self, filename, content): def _check_clock_domains(self, fragment, sync_domain=None): for clock_domain in fragment.domains.values(): if clock_domain.name != "sync" or (sync_domain is not None and - clock_domain is not sync_domain): + clock_domain is not sync_domain): raise ChipFlowError("Only a single clock domain, called 'sync', may be used") sync_domain = clock_domain @@ -252,10 +325,10 @@ def build(self, elaboratable, name="top"): filename_b = filename.encode("utf-8") if filename.endswith(".v") or filename.endswith(".vh"): yosys_script.append(b"read_verilog -defer <<" + filename_b + b"\n" + - content + b"\n" + filename_b) + content + b"\n" + filename_b) elif filename.endswith(".sv"): yosys_script.append(b"read_verilog -defer -sv <<" + filename_b + b"\n" + - content + b"\n" + filename_b) + content + b"\n" + filename_b) else: raise ValueError(f"File `{filename}` is not supported by the build platform") yosys_script += [ @@ -275,3 +348,13 @@ def build(self, elaboratable, name="top"): "-o", output_rtlil.replace("\\", "/") ]) return output_rtlil + + def default_clock(m, platform, clock, reset): + # Clock generation + m.domains.sync = ClockDomain() + + clk = platform.request(clock) + m.d.comb += ClockSignal().eq(clk.i) + m.submodules.rst_sync = FFSynchronizer( + ~platform.request(reset).i, + ResetSignal()) diff --git a/chipflow_lib/platforms/sim.py b/chipflow_lib/platforms/sim.py index 19d5540e..d87286d5 100644 --- a/chipflow_lib/platforms/sim.py +++ b/chipflow_lib/platforms/sim.py @@ -1,7 +1,5 @@ # SPDX-License-Identifier: BSD-2-Clause -import argparse -import sys import os from pathlib import Path @@ -13,8 +11,6 @@ class SimPlatform: - from ..providers import sim as providers - def __init__(self): self.build_dir = os.path.join(os.environ['CHIPFLOW_ROOT'], 'build', 'sim') self.extra_files = dict() diff --git a/chipflow_lib/platforms/utils.py b/chipflow_lib/platforms/utils.py new file mode 100644 index 00000000..c8bcbe6b --- /dev/null +++ b/chipflow_lib/platforms/utils.py @@ -0,0 +1,421 @@ +import abc +import enum +import itertools +import logging +import pathlib +import pydantic + +from collections import OrderedDict, deque +from collections.abc import MutableMapping +from pprint import pformat +from typing import Set, List, Dict, Optional, Union, Literal + +from amaranth.lib import wiring, io, meta +from amaranth.lib.wiring import In, Out +from pydantic import BaseModel, ConfigDict + +from .. import ChipFlowError, _ensure_chipflow_root, _get_cls_by_reference + + +__all__ = ['PIN_ANNOTATION_SCHEMA', 'PinSignature', + 'OutputPinSignature', 'InputPinSignature', 'BidirPinSignature', + 'load_pinlock', "PACKAGE_DEFINITIONS", 'top_interfaces'] + + +logger = logging.getLogger(__name__) + + +def _chipflow_schema_uri(name: str, version: int) -> str: + return f"https://api.chipflow.com/schemas/{version}/{name}" + + +class _PinAnnotationModel(BaseModel): + model_config = ConfigDict(use_enum_values=True) + direction: io.Direction + width: int + + @classmethod + def _annotation_schema(cls): + schema = _PinAnnotationModel.model_json_schema() + schema['$schema'] = "https://json-schema.org/draft/2020-12/schema" + schema['$id'] = _chipflow_schema_uri("pin-annotation", 0) + return schema + + def __init__(self, **kwargs): + kwargs['url'] = _chipflow_schema_uri("pin-annotation", 0) + super().__init__(**kwargs) + + +class _PinAnnotation(meta.Annotation): + schema = _PinAnnotationModel._annotation_schema() + + def __init__(self, **kwargs): + self.model = _PinAnnotationModel(**kwargs) + + @property + def origin(self): # type: ignore + return self.model + + def as_json(self): # type: ignore + return self.model.model_dump() + + +PIN_ANNOTATION_SCHEMA = str(_chipflow_schema_uri("pin-annotation", 0)) + + +class PinSignature(wiring.Signature): + """Amaranth Signtaure used to decorate wires that would + usually be brought out onto a pin on the package. + """ + + def __init__(self, direction, width=1, init=None): + self._direction = direction + self._width = width + self._init = init + match direction: + case io.Direction.Bidir: + sig = { + "o": Out(width), + "oe": Out(1), + "i": In(width) + } + case io.Direction.Input: + sig = {"i": In(width)} + case io.Direction.Output: + sig = {"o": Out(width)} + case _: + assert False + + super().__init__(sig) + + def annotations(self, *args): + annotations = wiring.Signature.annotations(self, *args) + pin_annotation = _PinAnnotation(direction=self._direction, width=self._width) + return annotations + (pin_annotation,) + + def __repr__(self): + return f"PinSignature({self._direction}, {self._width})" + + +def OutputPinSignature(width, **kwargs): + return PinSignature(io.Direction.Output, width=width, **kwargs) + + +def InputPinSignature(width, **kwargs): + return PinSignature(io.Direction.Input, width=width, **kwargs) + + +def BidirPinSignature(width, **kwargs): + return PinSignature(io.Direction.Bidir, width=width, **kwargs) + + +Pin = Union[tuple, str] +PinSet = Set[Pin] +PinList = List[Pin] +Pins = Union[PinSet, PinList] + + +class _Side(enum.IntEnum): + N = 1 + E = 2 + S = 3 + W = 4 + + def __str__(self): + return f'{self.name}' + + +def _group_consecutive_items(ordering: PinList, lst: PinList) -> OrderedDict[int, List[PinList]]: + if not lst: + return {} + + grouped = [] + last = lst[0] + current_group = [last] + + logger.debug(f"_group_consecutive_items starting with {current_group}") + + for item in lst[1:]: + idx = ordering.index(last) + next = ordering[idx + 1] if idx < len(ordering) - 1 else None + logger.debug(f"inspecting {item}, index {idx}, next {next}") + if item == next: + current_group.append(item) + logger.debug("found consecutive, adding to current group") + else: + logger.debug("found nonconsecutive, creating new group") + grouped.append(current_group) + current_group = [item] + last = item + + grouped.append(current_group) + d = {} + for g in grouped: + # logger.debug(f"adding to group {len(g)} pins {g}") + d.setdefault(len(g), []).append(g) + return d + + +def _find_contiguous_sequence(ordering: PinList, lst: PinList, total: int) -> PinList: + """Find the next sequence of n consecutive numbers in a sorted list + + Args: + lst: Sorted list of numbers + n: Length of consecutive sequence to find + + Returns: + A slice indexing the first sequence of n consecutive numbers found within the given list + if unable to find a consecutive list, allocate as contigously as possible + """ + if not lst or len(lst) < total: + raise ChipFlowError("Invalid request to find_contiguous_argument") + + grouped = _group_consecutive_items(ordering, lst) + + ret = [] + n = total + + # start with longest contiguous section, then continue into following sections + keys = deque(grouped.keys()) + best = max(keys) + start = keys.index(best) + keys.rotate(start) + + for k in keys: + for g in grouped[k]: + assert n + len(ret) == total + if k >= n: + ret += g[0:min(n, k)] + return ret + else: + n = n - k + ret += g[0:k] + + return ret + + +class _BasePackageDef(pydantic.BaseModel, abc.ABC): + """ + Abstract base class for the definition of a package + """ + # Used by pydantic to differentate when deserialising, + # override appropriately when you subclass + type: Literal["_BasePackageDef"] = "_BasePackageDef" + name: str + + @property + @abc.abstractmethod + def pins(self) -> PinSet: + ... + + @abc.abstractmethod + def allocate(self, available: PinSet, width: int) -> PinList: + ... + + def to_string(pins: Pins): + return [''.join(map(str, t)) for t in pins] + + def sortpins(self, pins: Pins) -> PinList: + return list(pins).sort() + + +class _BareDiePackageDef(_BasePackageDef): + """Definition of a package with pins on four sides, labelled north, south, east, west + with an integer identifier within each side. + """ + + # Used by pydantic to differentate when deserialising + type: Literal["_QuadPackageDef"] = "_QuadPackageDef" + + width: int + height: int + + def model_post_init(self, __context): + self._ordered_pins = sorted( + list(itertools.product((_Side.N, _Side.S), range(self.width))) + + list(itertools.product((_Side.W, _Side.E), range(self.height)))) + return super().model_post_init(__context) + + @property + def pins(self) -> PinSet: + return set(self._ordered_pins) + + def allocate(self, available: PinSet, width: int) -> PinList: + avail_n = self.sortpins(available) + logger.debug(f"_BareDiePackageDef.allocate {width} from {len(avail_n)} remaining") + ret = _find_contiguous_sequence(self._ordered_pins, avail_n, width) + logger.debug(f"_BareDiePackageDef.returned {ret}") + assert len(ret) == width + return ret + + +class _QuadPackageDef(_BasePackageDef): + """Definiton of a PGA package with `size` pins + + This is package with `size` pins, numbered, with the assumption that adjacent pins + are numbered close together. + """ + + # Used by pydantic to differentate when deserialising + type: Literal["_PGAPackageDef"] = "_PGAPackageDef" + + width:int + height: int + + def model_post_init(self, __context): + self._ordered_pins = sorted( + [str(i) for i in range(1, self.width * 2 + self.height * 2)]) + return super().model_post_init(__context) + + + @property + def pins(self) -> PinSet: + return set(self._ordered_pins) + + def allocate(self, available: Set[str], width: int) -> List[str]: + avail_n = sorted(available) + logger.debug(f"QuadPackageDef.allocate {width} from {len(avail_n)} remaining: {available}") + ret = _find_contiguous_sequence(self._ordered_pins, avail_n, width) + logger.debug(f"QuadPackageDef.returned {ret}") + assert len(ret) == width + return ret + + def sortpins(self, pins: Union[List[str], Set[str]]) -> List[str]: + return sorted(list(pins), key=int) + + +# Add any new package types to both PACKAGE_DEFINITIONS and the PackageDef union +PACKAGE_DEFINITIONS = { + "pga144": _QuadPackageDef(name="pga144", width=36, height=36), + "cf20": _BareDiePackageDef(name="cf20", width=7, height=3) +} + +PackageDef = Union[_QuadPackageDef, _BasePackageDef] + + +class Port(pydantic.BaseModel): + type: str + pins: List[str] + direction: Optional[str] = None + + @property + def width(self): + return len(self.pins) + + +class Package(pydantic.BaseModel): + package_type: PackageDef = pydantic.Field(discriminator="type") + power: Dict[str, Port] = {} + clocks: Dict[str, Port] = {} + resets: Dict[str, Port] = {} + + def check_pad(self, name: str, defn: dict): + match defn: + case {"type": "clock"}: + return self.clocks[name] if name in self.clocks else None + case {"type": "reset"}: + return self.resets[name] if name in self.clocks else None + case {"type": "power"}: + return self.power[name] if name in self.power else None + case {"type": "ground"}: + return self.power[name] if name in self.power else None + case _: + return None + + def add_pad(self, name: str, defn: dict): + match defn: + case {"type": "clock", "loc": loc}: + self.clocks[name] = Port(type="clock", pins=[loc], direction=io.Direction.Input) + case {"type": "reset", "loc": loc}: + self.resets[name] = Port(type="reset", pins=[loc], direction=io.Direction.Input) + case {"type": "power", "loc": loc}: + self.power[name] = Port(type="power", pins=[loc]) + case {"type": "ground", "loc": loc}: + self.power[name] = Port(type="ground", pins=[loc]) + case _: + pass + + +_Interface = Dict[str, Dict[str, Port]] + + +class PortMap(pydantic.RootModel[Dict[str, _Interface]], MutableMapping): + def __getitem__(self, key: str): + return self.root[key] + + def __setitem__(self, key: str, value: _Interface): + self.root[key] = value + + def __delitem__(self, key): + del self.root[key] + + def __iter__(self): + return iter(self.root) + + def __len__(self): + return len(self.root) + + def add_port(self, component: str, interface: str, port_name: str, port: Port): + if component not in self: + self[component] = {} + if interface not in self[component]: + self[component][interface] = {} + self[component][interface][port_name] = port + + def add_ports(self, component: str, interface: str, ports: Dict[str, Port]): + if component not in self: + self[component] = {} + self[component][interface] = ports + + def get_ports(self, component: str, name: str) -> Dict[str, Port]: + if component not in self: + return None + return self[component][name] + + +class LockFile(pydantic.BaseModel): + """ + Representation of a pin lock file. + + Attributes: + package: Information about package, power, clocks, reset etc + port_map: Mapping of components to interfaces to port + metadata: Amaranth metadata, for reference + """ + package: Package + port_map: PortMap + metadata: dict + + +def load_pinlock(): + chipflow_root = _ensure_chipflow_root() + lockfile = pathlib.Path(chipflow_root, 'pins.lock') + if lockfile.exists(): + json = lockfile.read_text() + return LockFile.model_validate_json(json) + raise ChipFlowError("Lockfile pins.lock not found. Run `chipflow pin lock`") + + +def top_interfaces(config): + interfaces = {} + top_components = config["chipflow"]["top"].items() + component_configs = {} + top = {} + + for name, conf in top_components: + if '.' in name: + assert conf is dict + logger.debug("Config found for {name}") + component_configs[name.split('.')[0]] = conf + + for name, ref in top_components: + cls = _get_cls_by_reference(ref, context=f"top component: {name}") + if name in component_configs: + top[name] = cls(component_configs[name]) + else: + top[name] = cls() + logger.debug(f"top members for {name}:\n{pformat(top[name].metadata.origin.signature.members)}") + # logger.debug(f"adding\n'{name}':{pformat(top[name].metadata.as_json())} to interfaces") + interfaces[name] = top[name].metadata.as_json() + + return top, interfaces diff --git a/chipflow_lib/providers/__init__.py b/chipflow_lib/providers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/chipflow_lib/providers/board_ulx3s.py b/chipflow_lib/providers/board_ulx3s.py deleted file mode 100644 index 7c38c008..00000000 --- a/chipflow_lib/providers/board_ulx3s.py +++ /dev/null @@ -1,150 +0,0 @@ -# SPDX-License-Identifier: BSD-2-Clause - -from amaranth import * -from amaranth_boards.ulx3s import * -from amaranth.lib.cdc import ResetSynchronizer -from amaranth.lib import io, wiring -from amaranth.lib.wiring import In - -from amaranth_soc import gpio - -from amaranth_orchard.memory.spimemio import QSPIPins -from amaranth_orchard.io.uart import UARTPins -from amaranth_orchard.memory.hyperram import HyperRAMPins - - -class QSPIFlashProvider(Elaboratable): - def __init__(self): - self.pins = QSPIPins() - - def elaborate(self, platform): - m = Module() - - flash = platform.request("spi_flash", dir=dict(cs='-', copi='-', cipo='-', wp='-', hold='-')) - # Flash clock requires a special primitive to access in ECP5 - m.submodules.usrmclk = Instance( - "USRMCLK", - i_USRMCLKI=self.pins.clk_o, - i_USRMCLKTS=ResetSignal(), # tristate in reset for programmer accesss - a_keep=1, - ) - # IO pins and buffers - m.submodules += Instance( - "OBZ", - o_O=flash.cs.io, - i_I=self.pins.csn_o, - i_T=ResetSignal(), - ) - # Pins in order - data_pins = ["copi", "cipo", "wp", "hold"] - - for i in range(4): - m.submodules += Instance( - "BB", - io_B=getattr(flash, data_pins[i]).io, - i_I=self.pins.d_o[i], - i_T=~self.pins.d_oe[i], - o_O=self.pins.d_i[i] - ) - return m - - -class LEDGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(8) - - def elaborate(self, platform): - m = Module() - for i in range(8): - led = io.Buffer("o", platform.request("led", i, dir="-")) - m.submodules[f"led_{i}"] = led - m.d.comb += led.o.eq(self.pins[i].o) - return m - - -class ButtonGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(2) - - def elaborate(self, platform): - m = Module() - for i in range(2): - btn = io.Buffer("i", platform.request("button_fire", i, dir="-")) - m.submodules[f"btn_{i}"] = btn - m.d.comb += self.pins[i].i.eq(btn.i) - return m - - -class UARTProvider(Elaboratable): - def __init__(self): - self.pins = UARTPins() - - def elaborate(self, platform): - m = Module() - uart_pins = platform.request("uart", 0, dir=dict(rx="-", tx="-", rts="-", dtr="-")) - m.submodules.uart_rx = uart_rx = io.Buffer("i", uart_pins.rx) - m.submodules.uart_tx = uart_tx = io.Buffer("o", uart_pins.tx) - m.d.comb += [ - uart_tx.o.eq(self.pins.tx_o), - self.pins.rx_i.eq(uart_rx.i), - ] - return m - - -class HyperRAMProvider(Elaboratable): - def __init__(self): - self.pins = HyperRAMPins(cs_count=4) - - def elaborate(self, platform): - # Dual HyperRAM PMOD, starting at GPIO 0+/- - platform.add_resources([ - Resource( - "hyperram", - 0, - Subsignal("csn", Pins("9- 9+ 10- 10+", conn=("gpio", 0), dir='o')), - Subsignal("rstn", Pins("8+", conn=("gpio", 0), dir='o')), - Subsignal("clk", Pins("8-", conn=("gpio", 0), dir='o')), - Subsignal("rwds", Pins("7+", conn=("gpio", 0), dir='io')), - - Subsignal("dq", Pins("3- 2- 1- 0- 0+ 1+ 2+ 3+", conn=("gpio", 0), dir='io')), - - Attrs(IO_TYPE="LVCMOS33"), - ) - ]) - - hyperram = platform.request("hyperram", 0) - - m = Module() - m.d.comb += [ - hyperram.clk.o.eq(self.pins.clk_o), - hyperram.csn.o.eq(self.pins.csn_o), - hyperram.rstn.o.eq(self.pins.rstn_o), - - hyperram.rwds.o.eq(self.pins.rwds_o), - hyperram.rwds.oe.eq(self.pins.rwds_oe), - self.pins.rwds_i.eq(hyperram.rwds.i), - - hyperram.dq.o.eq(self.pins.dq_o), - hyperram.dq.oe.eq(self.pins.dq_oe), - self.pins.dq_i.eq(hyperram.dq.i), - ] - return m - - -class JTAGProvider(Elaboratable): - def __init__(self, cpu): - pass - - def elaborate(self, platform): - return Module() # JTAG is not connected anywhere - - -class ClockResetProvider(Elaboratable): - def __init__(self): - pass - - def elaborate(self, platform): - m = Module() - m.submodules.clk25 = clk25 = io.Buffer("i", platform.request("clk25", dir="-")) - m.d.comb += ClockSignal("sync").eq(clk25.i) - m.submodules.btn_pwr = btn_pwr = io.Buffer("i", platform.request("button_pwr", dir="-")) - m.submodules += ResetSynchronizer(btn_pwr.i, domain="sync") - return m diff --git a/chipflow_lib/providers/silicon.py b/chipflow_lib/providers/silicon.py deleted file mode 100644 index 71e04293..00000000 --- a/chipflow_lib/providers/silicon.py +++ /dev/null @@ -1,132 +0,0 @@ -# SPDX-License-Identifier: BSD-2-Clause - -from amaranth import * -from amaranth.lib.cdc import FFSynchronizer -from amaranth.lib import wiring -from amaranth.lib.wiring import In - -from amaranth_soc import gpio - -from amaranth_orchard.memory.spimemio import QSPIPins -from amaranth_orchard.io.uart import UARTPins -from amaranth_orchard.memory.hyperram import HyperRAMPins - - -class QSPIFlashProvider(Elaboratable): - def __init__(self): - self.pins = QSPIPins() - - def elaborate(self, platform): - m = Module() - m.d.comb += [ - platform.request("flash_clk").o.eq(self.pins.clk_o), - platform.request("flash_csn").o.eq(self.pins.csn_o), - ] - for index in range(4): - pin = platform.request(f"flash_d{index}") - m.d.comb += [ - self.pins.d_i[index].eq(pin.i), - pin.o.eq(self.pins.d_o[index]), - pin.oe.eq(self.pins.d_oe[index]) - ] - return m - - -class LEDGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(8) - - def elaborate(self, platform): - m = Module() - for index in range(8): - pin = platform.request(f"gpio_{index}") - m.d.comb += [ - self.pins[index].i.eq(pin.i), - pin.o.eq(self.pins[index].o), - pin.oe.eq(self.pins[index].oe), - ] - return m - - -class ButtonGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(2) - - def elaborate(self, platform): - m = Module() - for index in range(2): - pin = platform.request(f"btn_{index}") - m.d.comb += [ - self.pins[index].i.eq(pin.i), - pin.o.eq(self.pins[index].o), - pin.oe.eq(self.pins[index].oe), - ] - return m - - -class UARTProvider(Elaboratable): - def __init__(self): - self.pins = UARTPins() - - def elaborate(self, platform): - m = Module() - m.d.comb += [ - platform.request("uart_tx").o.eq(self.pins.tx_o), - self.pins.rx_i.eq(platform.request("uart_rx").i), - ] - return m - - -class HyperRAMProvider(Elaboratable): - def __init__(self): - self.pins = HyperRAMPins(cs_count=4) - - def elaborate(self, platform): - m = Module() - m.d.comb += [ - platform.request("ram_clk").o.eq(self.pins.clk_o), - platform.request("ram_rstn").o.eq(self.pins.rstn_o), - ] - - for index in range(4): - platform.request(f"ram_csn_{index}").o.eq(self.pins.csn_o[index]), - - rwds = platform.request("ram_rwds") - m.d.comb += [ - rwds.o.eq(self.pins.rwds_o), - rwds.oe.eq(self.pins.rwds_oe), - self.pins.rwds_i.eq(rwds.i), - ] - - for index in range(8): - dq = platform.request(f"ram_dq_{index}") - m.d.comb += [ - dq.o.eq(self.pins.dq_o[index]), - dq.oe.eq(self.pins.dq_oe[index]), - self.pins.dq_i[index].eq(dq.i), - ] - - return m - - -class JTAGProvider(Elaboratable): - def __init__(self, cpu): - self.cpu = cpu - - def elaborate(self, platform): - m = Module() - m.d.comb += [ - self.cpu.jtag_tck.eq(platform.request("jtag_tck").i), - self.cpu.jtag_tdi.eq(platform.request("jtag_tdi").i), - self.cpu.jtag_tms.eq(platform.request("jtag_tms").i), - platform.request("jtag_tdo").o.eq(self.cpu.jtag_tdo), - ] - return m - - -class ClockResetProvider(Elaboratable): - def elaborate(self, platform): - m = Module() - m.d.comb += ClockSignal("sync").eq(platform.request("sys_clk").i) - m.submodules.rst_sync = FFSynchronizer( - ~platform.request("sys_rstn").i, - ResetSignal("sync")) - return m diff --git a/chipflow_lib/providers/sim.py b/chipflow_lib/providers/sim.py deleted file mode 100644 index bdf9bef2..00000000 --- a/chipflow_lib/providers/sim.py +++ /dev/null @@ -1,68 +0,0 @@ -# SPDX-License-Identifier: BSD-2-Clause - -from amaranth import * -from amaranth.lib import wiring -from amaranth.lib.wiring import In - -from amaranth_soc import gpio - -from amaranth_orchard.memory.spimemio import QSPIPins -from amaranth_orchard.io.uart import UARTPins -from amaranth_orchard.memory.hyperram import HyperRAMPins - - -class QSPIFlashProvider(Elaboratable): - def __init__(self): - self.pins = QSPIPins() - - def elaborate(self, platform): - return platform.add_model("spiflash_model", self.pins, edge_det=['clk_o', 'csn_o']) - - -class LEDGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(8) - - def elaborate(self, platform): - return Module() - - -class ButtonGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(2) - - def elaborate(self, platform): - m = Module() - for i in range(2): - m.d.comb += self.pins[i].i.eq(platform.buttons[i]) - return m - - -class UARTProvider(Elaboratable): - def __init__(self): - self.pins = UARTPins() - - def elaborate(self, platform): - return platform.add_model("uart_model", self.pins, edge_det=[]) - - -class HyperRAMProvider(Elaboratable): - def __init__(self): - self.pins = HyperRAMPins(cs_count=4) - - def elaborate(self, platform): - return platform.add_model("hyperram_model", hram, edge_det=['clk_o']) - - -class JTAGProvider(Elaboratable): - def __init__(self, cpu): - pass - - def elaborate(self, platform): - return Module() # JTAG is not connected anywhere - - -class ClockResetProvider(Elaboratable): - def elaborate(self, platform): - m = Module() - m.d.comb += ClockSignal("sync").eq(platform.clk) - m.d.comb += ResetSignal("sync").eq(platform.rst) - return m diff --git a/chipflow_lib/steps/silicon.py b/chipflow_lib/steps/silicon.py index 1116ff8c..0ecbd187 100644 --- a/chipflow_lib/steps/silicon.py +++ b/chipflow_lib/steps/silicon.py @@ -1,32 +1,61 @@ # SPDX-License-Identifier: BSD-2-Clause +import argparse +import importlib.metadata +import inspect +import json +import logging import os +import requests +import subprocess import sys import time -import json -import pprint -import inspect -import argparse -import subprocess -import importlib.metadata -import requests +import dotenv + +from amaranth import * from .. import ChipFlowError -from ..platforms.silicon import SiliconPlatform +from ..platforms import SiliconPlatform, top_interfaces + + +logger = logging.getLogger(__name__) + + +class SiliconTop(Elaboratable): + def __init__(self, config={}): + self._config = config + + def elaborate(self, platform: SiliconPlatform): + m = Module() + + platform.instantiate_ports(m) + + # heartbeat led (to confirm clock/reset alive) + if ("config" in self._config["chipflow"]["silicon"] and + self._config["chipflow"]["silicon"]["debug"]["heartbeat"]): + heartbeat_ctr = Signal(23) + m.d.sync += heartbeat_ctr.eq(heartbeat_ctr + 1) + m.d.comb += platform.request("heartbeat").o.eq(heartbeat_ctr[-1]) + + top, interfaces = top_interfaces(self._config) + for n, t in top.items(): + setattr(m.submodules, n, t) + + return m class SiliconStep: """Prepare and submit the design for an ASIC.""" - def __init__(self, config): - self.project_id = config["chipflow"].get("project_id") + self.config = config + self.project_name = config["chipflow"].get("project_name") self.silicon_config = config["chipflow"]["silicon"] - self.platform = SiliconPlatform(pads=self.silicon_config["pads"]) + self.platform = SiliconPlatform(config) def build_cli_parser(self, parser): action_argument = parser.add_subparsers(dest="action") - prepare_subparser = action_argument.add_parser( + action_argument.add_parser( "prepare", help=inspect.getdoc(self.prepare).splitlines()[0]) submit_subparser = action_argument.add_parser( "submit", help=inspect.getdoc(self.submit).splitlines()[0]) @@ -36,7 +65,7 @@ def build_cli_parser(self, parser): def run_cli(self, args): if args.action == "submit" and not args.dry_run: - if self.project_id is None: + if self.project_name is None: raise ChipFlowError( "Key `chipflow.project_id` is not defined in chipflow.toml; " "see https://chipflow.io/beta for details on how to join the beta") @@ -55,11 +84,12 @@ def prepare(self): Returns the path to the RTLIL file. """ - raise NotImplementedError + return self.platform.build(SiliconTop(self.config), name=self.config["chipflow"]["project_name"]) def submit(self, rtlil_path, *, dry_run=False): """Submit the design to the ChipFlow cloud builder. """ + dotenv.load_dotenv() git_head = subprocess.check_output( ["git", "-C", os.environ["CHIPFLOW_ROOT"], "rev-parse", "HEAD"], @@ -69,8 +99,8 @@ def submit(self, rtlil_path, *, dry_run=False): "status", "--porcelain", "--untracked-files=no"])) submission_name = git_head if git_dirty: + logging.warning("Git tree is dirty, submitting anyway!") submission_name += f"-dirty.{time.strftime('%Y%m%d%M%H%S', time.gmtime())}" - dep_versions = { "python": sys.version.split()[0] } @@ -87,18 +117,27 @@ def submit(self, rtlil_path, *, dry_run=False): except importlib.metadata.PackageNotFoundError: dep_versions[package] = None data = { - "projectId": self.project_id, + "projectId": self.project_name, "name": submission_name, } + + pads = {} + for iface, port in self.platform._ports.items(): + width = len(port.pins) + print(f"iface={iface}, port={port}, dir={port.direction}, width={width}") + if width > 1: + for i in range(width): + padname = f"{iface}{i}" + print(f"padname={padname}, port={port}, loc={port.pins[i:i+1]}, " + f"dir={port.direction}, width={width}") + pads[padname] = {'loc': port.pins[i:i+1], 'dir': port.direction} + config = { "dependency_versions": dep_versions, "silicon": { "process": self.silicon_config["process"], - "pad_ring": self.silicon_config["pad_ring"], - "pads": { - pad_name: self.silicon_config["pads"][pad_name] - for pad_name in self.platform._ports - }, + "package": self.silicon_config["package"], + "pads": pads, "power": self.silicon_config.get("power", {}) } } @@ -107,6 +146,8 @@ def submit(self, rtlil_path, *, dry_run=False): print(f"files['config']=\n{json.dumps(config, indent=2)}") return + logger.info(f"Submitting {submission_name} for project {self.project_name}") + resp = requests.post( os.environ.get("CHIPFLOW_API_ENDPOINT", "https://app.chipflow-infra.com/api/builds"), auth=(os.environ["CHIPFLOW_API_KEY_ID"], os.environ["CHIPFLOW_API_KEY_SECRET"]), @@ -135,3 +176,5 @@ def submit(self, rtlil_path, *, dry_run=False): else: print(f"{resp_data['msg']} (#{resp_data['id']}: {resp_data['name']}); " f"{resp_data['url']}") + else: + ChipFlowError(f"Unexpected response from API: {resp}") diff --git a/docs/_static/chipflow-logo.png b/docs/_static/chipflow-logo.png new file mode 100644 index 00000000..81ced7d5 Binary files /dev/null and b/docs/_static/chipflow-logo.png differ diff --git a/docs/_static/chipflow-logo.svg b/docs/_static/chipflow-logo.svg new file mode 100644 index 00000000..9d3aa488 --- /dev/null +++ b/docs/_static/chipflow-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/chipflow-toml-guide.rst b/docs/chipflow-toml-guide.rst index 0990e83e..07d7cbc0 100644 --- a/docs/chipflow-toml-guide.rst +++ b/docs/chipflow-toml-guide.rst @@ -1,5 +1,5 @@ -Intro to chipflow.toml -====================== +Intro to ``chipflow.toml`` +========================== The ``chipflow.toml`` file provides configuration for your design with the ChipFlow platform. @@ -14,25 +14,55 @@ Let's start with a typical example: # Assert that example-chipflow.toml matches the current config schema. If # this test fails, then its likely that the content in this file will need # to be updated. - from chipflow_lib.cli import _parse_config_file + from chipflow_lib import _parse_config_file _parse_config_file("docs/example-chipflow.toml") -project_id -=========== +``[chipflow]`` +-------------- -The ``project_id`` is set to the project ID which you can get from the ChipFlow app project page. +.. code-block:: TOML + + [chipflow] + project_name = "my_project" + + +The ``project_name`` is a human-readable identifier for this project. If not set, the tool and library will use the project name configured in ``pyproject.toml``. + +``[chipflow.steps]`` +-------------------- + +The ``steps`` section allows overriding or addition to the standard steps available from `chipflow_lib`_. + +For example, if you want to override the standard silicon preparation step, you could derive from :class:`chipflow_lib.steps.silicon.SiliconStep`, add your custom functionality +and add the following to your `chipflow.toml`, with the appropriate Python `qualified name`_ : + +.. code-block:: TOML + + [chipflow.stepe] + silicon = "my_design.steps.silicon:SiliconStep" -steps -===== -The ``steps`` define the Python class which will be used as an entry point to these parts of the ChipFlow process. You probably won't need to change these if you're starting from an example repository. -silicon -======= +.. _chipflow_lib: https://github.com/ChipFlow/chipflow-lib] +.. _qualified name: https://docs.python.org/3/glossary.html#term-qualified-name -The ``silicon`` section sets the Foundry ``process`` we are targeting for manufacturing, and the physical ``pad_ring`` we want to place our design inside. -You'll choose the ``process`` and ``pad_ring`` based in the requirements of your design. + +``[chipflow.clocks]`` +--------------------- + +``[chipflow.silicon]`` +---------------------- + +.. code-block:: TOML + + [chipflow.silicon] + process = "ihp_sg13g2" + package = "pga144" + + +The ``silicon`` section sets the Foundry ``process`` we are targeting for manufacturing, and the physical ``package`` we want to place our design inside. +You'll choose the ``process`` and ``package`` based in the requirements of your design. Available processes ------------------- @@ -56,12 +86,8 @@ Available pad rings +----------+-----------+--------------------+------------------------------------+ | Pad ring | Pad count | Pad locations | Notes | +==========+===========+====================+====================================+ -|| caravel || TBC || TBC || The `Caravel Harness`_ contains | -|| || || || additional logic which wraps your | -|| || || || design. | -|| || || || It handles its own power pins. | +----------+-----------+--------------------+------------------------------------+ -|| cf20 || 20 || ``N1`` ... ``N7`` || | +|| cf20 || 20 || ``N1`` ... ``N7`` || Bare die package with 20 pins | || || || ``S1`` ... ``S7`` || | || || || ``E1`` ... ``E3`` || | || || || ``W1`` ... ``W3`` || | @@ -74,10 +100,12 @@ Available pad rings +----------+-----------+--------------------+------------------------------------+ + + silicon.pads ============ -The ``silicon.pads`` section lists the pads we will be using. +The ``silicon.pads`` section lists special pads. In general you are unlikely to need to add to this. For each pad, there's a label which is used by our design, and what ``type`` and ``loc`` each pad should be. @@ -86,8 +114,8 @@ type The ``type`` for each pad can be set to one of: -clk - External clock. +clock + External clock input. i Input. diff --git a/docs/conf.py b/docs/conf.py index 5950dec3..14e581d2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,16 +4,23 @@ # Add parent folder to path so we can pick up module import os import sys +sys.path.insert(0, os.path.abspath('../../chipflow_lib')) + +from chipflow_lib import __version__ + doctest_path = [os.path.abspath('..')] # -- Project information -project = 'ChipFlow' -copyright = 'ChipFlow' -author = 'ChipFlow' +project = 'chipflow-lib' +copyright = 'ChipFlow Limited, 2021-2025' +author = 'ChipFlow Platform Team' release = 'alpha' -version = '0.1.0' +version = __version__ + +master_doc = "index" + # -- General configuration @@ -21,15 +28,72 @@ 'sphinx.ext.duration', 'sphinx.ext.doctest', 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', + 'autoapi.extension', + 'sphinx.ext.napoleon' +] + +html_theme = 'sphinx_book_theme' +html_logo = '_static/chipflow-logo.svg' +html_title = "ChipFlow Platform Documentation" + + +html_sidebars = { + '**': [ + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + ] +} + +html_static_path = ['_static'] + +html_theme_options = { + "home_page_in_toc": True, + "repository_url": "https://github.com/ChipFlow/chipflow-lib", + "repository_branch": "master", + "path_to_docs": "docs", + "use_repository_button": True, + "use_edit_page_button": True, + "use_issues_button": True, + "show_navbar_depth": 3, + # "announcement": "v3.0.0 is now out! See the Changelog for details", +} + +autodoc_typehints = 'description' + +autoapi_dirs = ["../chipflow_lib"] +autoapi_options = [ + 'members', + 'undoc-members', + 'show-inheritance', + 'show-module-summary', + 'imported-members', ] intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'sphinx': ('https://www.sphinx-doc.org/en/master/', None), + 'py': ('https://docs.python.org/3/', None), + 'amaranth': ('https://amaranth-lang.org/docs/amaranth/v0.5.4/', None), + # 'chipflow': ('https://docs.chipflow.io/', None), } intersphinx_disabled_domains = ['std'] +# Napoleon settings +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = True +napoleon_include_private_with_doc = True +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True + +rst_prolog = """ +.. role:: py(code) + :language: python +""" + # -- Options for EPUB output epub_show_urls = 'footnote' diff --git a/docs/example-chipflow.toml b/docs/example-chipflow.toml index 5db4cfa0..3c7c31a5 100644 --- a/docs/example-chipflow.toml +++ b/docs/example-chipflow.toml @@ -1,38 +1,43 @@ [chipflow] -project_id = 8 +project_name = "test-chip" + +[chipflow.top] +soc = "my_design.design:MySoC" [chipflow.steps] -sim = "my_design.steps.sim:MySimStep" -board = "my_design.steps.board:MyBoardStep" -silicon = "my_design.steps.silicon:MySiliconStep" -software = "my_design.steps.software:MySoftwareStep" +silicon = "chipflow_lib.steps.silicon:SiliconStep" + +[chipflow.clocks] +default = 'sys_clk' + +[chipflow.resets] +default = 'sys_rst_n' [chipflow.silicon] -process = "sky130" -pad_ring = "caravel" +process = "gf130bcd" +package = "pga144" [chipflow.silicon.pads] -sys_clk = { type = "clk", loc = "0" } -sys_rstn = { type = "i", loc = "1" } -uart_tx = { type = "o", loc = "2" } -uart_rx = { type = "i", loc = "3" } -flash_clk = { type = "o", loc = "4" } -flash_csn = { type = "o", loc = "5" } -flash_d0 = { type = "io", loc = "6" } -flash_d1 = { type = "io", loc = "7" } -flash_d2 = { type = "io", loc = "8" } -flash_d3 = { type = "io", loc = "9" } -gpio_0 = { type = "io", loc = "10" } -gpio_1 = { type = "io", loc = "11" } -gpio_2 = { type = "io", loc = "12" } -gpio_3 = { type = "io", loc = "13" } -gpio_4 = { type = "io", loc = "14" } -gpio_5 = { type = "io", loc = "15" } -gpio_6 = { type = "io", loc = "16" } -gpio_7 = { type = "io", loc = "17" } -btn_0 = { type = "io", loc = "18" } -btn_1 = { type = "io", loc = "19" } -jtag_tck = { type = "i", loc = "33" } -jtag_tms = { type = "i", loc = "34" } -jtag_tdi = { type = "i", loc = "35" } -jtag_tdo = { type = "o", loc = "36" } +# System +sys_clk = { type = "clock", loc = "114" } +sys_rst_n = { type = "reset", loc = "115" } + +[chipflow.silicon.power] +dvss0 = { type = "power", loc = "1" } +dvdd0 = { type = "ground", loc = "9" } +vss0 = { type = "power", loc = "17" } +vdd0 = { type = "ground", loc = "25" } +dvss1 = { type = "power", loc = "33" } +dvdd1 = { type = "ground", loc = "41" } +vss1 = { type = "power", loc = "49" } +vdd1 = { type = "ground", loc = "57" } +dvss2 = { type = "power", loc = "65" } +dvdd2 = { type = "ground", loc = "73" } +vss2 = { type = "power", loc = "81" } +vdd2 = { type = "ground", loc = "89" } +dvss3 = { type = "power", loc = "97" } +dvdd3 = { type = "ground", loc = "105" } +vss3 = { type = "power", loc = "113" } +vdd3 = { type = "ground", loc = "121" } +dvss4 = { type = "power", loc = "129" } +dvdd4 = { type = "ground", loc = "137" } diff --git a/docs/index.rst b/docs/index.rst index a61fc9fe..eaece24e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,9 +1,16 @@ -chipflow-lib documentation -========================== +ChipFlow Library Documentation +------------------------------ + +.. image: _static/chipflow-logo.svg + :width: 200px + :class: sd-m-auto + :name: landing-page-logo + + +.. rubric: ChipFlow IC Design Platform -Contents --------- .. toctree:: chipflow-toml-guide + diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 00000000..80606cf1 --- /dev/null +++ b/pdm.lock @@ -0,0 +1,1397 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default", "lint", "test"] +strategy = ["inherit_metadata"] +lock_version = "4.5.0" +content_hash = "sha256:c101435debd2c023d993687fb5bf233f633eecf35b47e75dc57b0e4dc6c38a12" + +[[metadata.targets]] +requires_python = "~=3.10" + +[[package]] +name = "accessible-pygments" +version = "0.0.5" +requires_python = ">=3.9" +summary = "A collection of accessible pygments styles" +groups = ["default"] +dependencies = [ + "pygments>=1.5", +] +files = [ + {file = "accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7"}, + {file = "accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872"}, +] + +[[package]] +name = "alabaster" +version = "0.7.16" +requires_python = ">=3.9" +summary = "A light, configurable Sphinx theme" +groups = ["default", "test"] +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] + +[[package]] +name = "amaranth" +version = "0.5.4" +requires_python = "~=3.8" +summary = "Amaranth hardware definition language" +groups = ["default"] +dependencies = [ + "Jinja2~=3.0", + "importlib-resources; python_version < \"3.9\"", + "jschon~=0.11.1", + "pyvcd<0.5,>=0.2.2", +] +files = [ + {file = "amaranth-0.5.4-py3-none-any.whl", hash = "sha256:ce7473b4220acc78474474fd132177ca545fb144d4e69e1c7dbfc2ed7d32bcf3"}, + {file = "amaranth-0.5.4.tar.gz", hash = "sha256:a0ea7ffe358ab00d5524b53c43277d279723437be146c8250e26f6b349b8a4fd"}, +] + +[[package]] +name = "amaranth-boards" +version = "0.1.dev253" +requires_python = "~=3.9" +git = "https://github.com/amaranth-lang/amaranth-boards" +revision = "9d97c4816288c9c2cc304d9280c2c63178d50d2f" +summary = "Board and connector definitions for Amaranth HDL" +groups = ["default"] +dependencies = [ + "amaranth<0.7,>=0.5", +] + +[[package]] +name = "amaranth-soc" +version = "0.1a1.dev24" +requires_python = "~=3.9" +git = "https://github.com/amaranth-lang/amaranth-soc" +revision = "5c43cf58f15d9cd9c69ff83c97997708d386b2dc" +summary = "System on Chip toolkit for Amaranth HDL" +groups = ["default"] +dependencies = [ + "amaranth<0.6,>=0.5", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +requires_python = ">=3.8" +summary = "Reusable constraint types to use with typing.Annotated" +groups = ["default"] +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.9\"", +] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "astroid" +version = "3.3.8" +requires_python = ">=3.9.0" +summary = "An abstract syntax tree for Python with inference support." +groups = ["default"] +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.11\"", +] +files = [ + {file = "astroid-3.3.8-py3-none-any.whl", hash = "sha256:187ccc0c248bfbba564826c26f070494f7bc964fd286b6d9fff4420e55de828c"}, + {file = "astroid-3.3.8.tar.gz", hash = "sha256:a88c7994f914a4ea8572fac479459f4955eeccc877be3f2d959a33273b0cf40b"}, +] + +[[package]] +name = "attrs" +version = "24.2.0" +requires_python = ">=3.7" +summary = "Classes Without Boilerplate" +groups = ["default"] +dependencies = [ + "importlib-metadata; python_version < \"3.8\"", +] +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[[package]] +name = "babel" +version = "2.17.0" +requires_python = ">=3.8" +summary = "Internationalization utilities" +groups = ["default", "test"] +dependencies = [ + "pytz>=2015.7; python_version < \"3.9\"", +] +files = [ + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.3" +requires_python = ">=3.7.0" +summary = "Screen-scraping library" +groups = ["default"] +dependencies = [ + "soupsieve>1.2", + "typing-extensions>=4.0.0", +] +files = [ + {file = "beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16"}, + {file = "beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b"}, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." +groups = ["default", "test"] +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +groups = ["default", "test"] +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "click" +version = "8.1.7" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +groups = ["default"] +dependencies = [ + "colorama; platform_system == \"Windows\"", + "importlib-metadata; python_version < \"3.8\"", +] +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[[package]] +name = "cloudpickle" +version = "3.1.0" +requires_python = ">=3.8" +summary = "Pickler class to extend the standard pickle.Pickler functionality" +groups = ["default"] +files = [ + {file = "cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e"}, + {file = "cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." +groups = ["default", "lint", "test"] +marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.6.10" +requires_python = ">=3.9" +summary = "Code coverage measurement for Python" +groups = ["lint", "test"] +files = [ + {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, + {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, + {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, + {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, + {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, + {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, + {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, + {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, + {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, + {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, + {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, + {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, + {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, + {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, + {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, + {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, +] + +[[package]] +name = "coverage" +version = "7.6.10" +extras = ["toml"] +requires_python = ">=3.9" +summary = "Code coverage measurement for Python" +groups = ["lint", "test"] +dependencies = [ + "coverage==7.6.10", + "tomli; python_full_version <= \"3.11.0a6\"", +] +files = [ + {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, + {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, + {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, + {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, + {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, + {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, + {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, + {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, + {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, + {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, + {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, + {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, + {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, + {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, + {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, + {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, +] + +[[package]] +name = "docutils" +version = "0.21.2" +requires_python = ">=3.9" +summary = "Docutils -- Python Documentation Utilities" +groups = ["default", "test"] +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + +[[package]] +name = "doit" +version = "0.36.0" +requires_python = ">=3.8" +summary = "doit - Automation Tool" +groups = ["default"] +dependencies = [ + "cloudpickle", + "importlib-metadata>=4.4", +] +files = [ + {file = "doit-0.36.0-py3-none-any.whl", hash = "sha256:ebc285f6666871b5300091c26eafdff3de968a6bd60ea35dd1e3fc6f2e32479a"}, + {file = "doit-0.36.0.tar.gz", hash = "sha256:71d07ccc9514cb22fe59d98999577665eaab57e16f644d04336ae0b4bae234bc"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" +groups = ["lint", "test"] +marker = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[[package]] +name = "idna" +version = "3.10" +requires_python = ">=3.6" +summary = "Internationalized Domain Names in Applications (IDNA)" +groups = ["default", "test"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Getting image size from png/jpeg/jpeg2000/gif file" +groups = ["default", "test"] +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +requires_python = ">=3.8" +summary = "Read metadata from Python packages" +groups = ["default"] +dependencies = [ + "typing-extensions>=3.6.4; python_version < \"3.8\"", + "zipp>=3.20", +] +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + +[[package]] +name = "importlib-resources" +version = "6.4.5" +requires_python = ">=3.8" +summary = "Read resources from Python packages" +groups = ["default"] +dependencies = [ + "zipp>=3.1.0; python_version < \"3.10\"", +] +files = [ + {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, + {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" +groups = ["lint", "test"] +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +requires_python = ">=3.7" +summary = "A very fast and expressive template engine." +groups = ["default", "test"] +dependencies = [ + "MarkupSafe>=2.0", +] +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[[package]] +name = "jschon" +version = "0.11.1" +requires_python = "~=3.8" +summary = "A JSON toolkit for Python developers." +groups = ["default"] +dependencies = [ + "rfc3986", +] +files = [ + {file = "jschon-0.11.1-py3-none-any.whl", hash = "sha256:2350e8b6747b17358022960f91208bea70de448b914827af3184d30e20500f0f"}, + {file = "jschon-0.11.1.tar.gz", hash = "sha256:c0ca0beab1f1694a03d726b91ed75ec604a7787af3ae91ead765f78215bf149f"}, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +requires_python = ">=3.8" +summary = "An implementation of JSON Schema validation for Python" +groups = ["default"] +dependencies = [ + "attrs>=22.2.0", + "importlib-resources>=1.4.0; python_version < \"3.9\"", + "jsonschema-specifications>=2023.03.6", + "pkgutil-resolve-name>=1.3.10; python_version < \"3.9\"", + "referencing>=0.28.4", + "rpds-py>=0.7.1", +] +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +requires_python = ">=3.9" +summary = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +groups = ["default"] +dependencies = [ + "referencing>=0.31.0", +] +files = [ + {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, + {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +requires_python = ">=3.9" +summary = "Safely add untrusted strings to HTML/XML markup." +groups = ["default", "test"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "packaging" +version = "24.2" +requires_python = ">=3.8" +summary = "Core utilities for Python packages" +groups = ["default", "lint", "test"] +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +requires_python = ">=3.8" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +groups = ["default"] +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +requires_python = ">=3.8" +summary = "plugin and hook calling mechanisms for python" +groups = ["lint", "test"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[[package]] +name = "pydantic" +version = "2.10.6" +requires_python = ">=3.8" +summary = "Data validation using Python type hints" +groups = ["default"] +dependencies = [ + "annotated-types>=0.6.0", + "pydantic-core==2.27.2", + "typing-extensions>=4.12.2", +] +files = [ + {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, + {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +requires_python = ">=3.8" +summary = "Core functionality for Pydantic validation and serialization" +groups = ["default"] +dependencies = [ + "typing-extensions!=4.7.0,>=4.6.0", +] +files = [ + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, +] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.16.1" +requires_python = ">=3.9" +summary = "Bootstrap-based Sphinx theme from the PyData community" +groups = ["default"] +dependencies = [ + "Babel", + "accessible-pygments", + "beautifulsoup4", + "docutils!=0.17.0", + "pygments>=2.7", + "sphinx>=6.1", + "typing-extensions", +] +files = [ + {file = "pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde"}, + {file = "pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7"}, +] + +[[package]] +name = "pygments" +version = "2.19.1" +requires_python = ">=3.8" +summary = "Pygments is a syntax highlighting package written in Python." +groups = ["default", "test"] +files = [ + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, +] + +[[package]] +name = "pytest" +version = "8.3.4" +requires_python = ">=3.8" +summary = "pytest: simple powerful testing with Python" +groups = ["lint", "test"] +dependencies = [ + "colorama; sys_platform == \"win32\"", + "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", + "iniconfig", + "packaging", + "pluggy<2,>=1.5", + "tomli>=1; python_version < \"3.11\"", +] +files = [ + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +requires_python = ">=3.9" +summary = "Pytest plugin for measuring coverage." +groups = ["lint", "test"] +dependencies = [ + "coverage[toml]>=7.5", + "pytest>=4.6", +] +files = [ + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +requires_python = ">=3.8" +summary = "Read key-value pairs from a .env file and set them as environment variables" +groups = ["default"] +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[[package]] +name = "pyvcd" +version = "0.4.1" +requires_python = ">=3.7" +summary = "Python VCD file support" +groups = ["default"] +files = [ + {file = "pyvcd-0.4.1-py2.py3-none-any.whl", hash = "sha256:3a4c71d4dce741f1155a2ed11a6278390a0816293068f6162ad9658d20f75578"}, + {file = "pyvcd-0.4.1.tar.gz", hash = "sha256:dc6275e95a7949b8236086ab2e6d03afede73441243ec5109c9ea89077f3d696"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +requires_python = ">=3.8" +summary = "YAML parser and emitter for Python" +groups = ["default"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "referencing" +version = "0.35.1" +requires_python = ">=3.8" +summary = "JSON Referencing + Python" +groups = ["default"] +dependencies = [ + "attrs>=22.2.0", + "rpds-py>=0.7.0", +] +files = [ + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +requires_python = ">=3.8" +summary = "Python HTTP for Humans." +groups = ["default", "test"] +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1", +] +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[[package]] +name = "rfc3986" +version = "2.0.0" +requires_python = ">=3.7" +summary = "Validating URI References per RFC 3986" +groups = ["default"] +files = [ + {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, + {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, +] + +[[package]] +name = "rpds-py" +version = "0.21.0" +requires_python = ">=3.9" +summary = "Python bindings to Rust's persistent data structures (rpds)" +groups = ["default"] +files = [ + {file = "rpds_py-0.21.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590"}, + {file = "rpds_py-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664"}, + {file = "rpds_py-0.21.0-cp310-none-win32.whl", hash = "sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682"}, + {file = "rpds_py-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8"}, + {file = "rpds_py-0.21.0-cp311-none-win32.whl", hash = "sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a"}, + {file = "rpds_py-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11"}, + {file = "rpds_py-0.21.0-cp312-none-win32.whl", hash = "sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952"}, + {file = "rpds_py-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976"}, + {file = "rpds_py-0.21.0-cp313-none-win32.whl", hash = "sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202"}, + {file = "rpds_py-0.21.0-cp313-none-win_amd64.whl", hash = "sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e"}, + {file = "rpds_py-0.21.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2c51d99c30091f72a3c5d126fad26236c3f75716b8b5e5cf8effb18889ced928"}, + {file = "rpds_py-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbd7504a10b0955ea287114f003b7ad62330c9e65ba012c6223dba646f6ffd05"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dcc4949be728ede49e6244eabd04064336012b37f5c2200e8ec8eb2988b209c"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f414da5c51bf350e4b7960644617c130140423882305f7574b6cf65a3081cecb"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9afe42102b40007f588666bc7de82451e10c6788f6f70984629db193849dced1"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b929c2bb6e29ab31f12a1117c39f7e6d6450419ab7464a4ea9b0b417174f044"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8404b3717da03cbf773a1d275d01fec84ea007754ed380f63dfc24fb76ce4592"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e12bb09678f38b7597b8346983d2323a6482dcd59e423d9448108c1be37cac9d"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58a0e345be4b18e6b8501d3b0aa540dad90caeed814c515e5206bb2ec26736fd"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c3761f62fcfccf0864cc4665b6e7c3f0c626f0380b41b8bd1ce322103fa3ef87"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c2b2f71c6ad6c2e4fc9ed9401080badd1469fa9889657ec3abea42a3d6b2e1ed"}, + {file = "rpds_py-0.21.0-cp39-none-win32.whl", hash = "sha256:b21747f79f360e790525e6f6438c7569ddbfb1b3197b9e65043f25c3c9b489d8"}, + {file = "rpds_py-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:0626238a43152918f9e72ede9a3b6ccc9e299adc8ade0d67c5e142d564c9a83d"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:031819f906bb146561af051c7cef4ba2003d28cff07efacef59da973ff7969ba"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b876f2bc27ab5954e2fd88890c071bd0ed18b9c50f6ec3de3c50a5ece612f7a6"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc5695c321e518d9f03b7ea6abb5ea3af4567766f9852ad1560f501b17588c7b"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4de1da871b5c0fd5537b26a6fc6814c3cc05cabe0c941db6e9044ffbb12f04a"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878f6fea96621fda5303a2867887686d7a198d9e0f8a40be100a63f5d60c88c9"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8eeec67590e94189f434c6d11c426892e396ae59e4801d17a93ac96b8c02a6c"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff2eba7f6c0cb523d7e9cff0903f2fe1feff8f0b2ceb6bd71c0e20a4dcee271"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a429b99337062877d7875e4ff1a51fe788424d522bd64a8c0a20ef3021fdb6ed"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d167e4dbbdac48bd58893c7e446684ad5d425b407f9336e04ab52e8b9194e2ed"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:4eb2de8a147ffe0626bfdc275fc6563aa7bf4b6db59cf0d44f0ccd6ca625a24e"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e78868e98f34f34a88e23ee9ccaeeec460e4eaf6db16d51d7a9b883e5e785a5e"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4991ca61656e3160cdaca4851151fd3f4a92e9eba5c7a530ab030d6aee96ec89"}, + {file = "rpds_py-0.21.0.tar.gz", hash = "sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db"}, +] + +[[package]] +name = "ruff" +version = "0.9.5" +requires_python = ">=3.7" +summary = "An extremely fast Python linter and code formatter, written in Rust." +groups = ["lint"] +files = [ + {file = "ruff-0.9.5-py3-none-linux_armv6l.whl", hash = "sha256:d466d2abc05f39018d53f681fa1c0ffe9570e6d73cde1b65d23bb557c846f442"}, + {file = "ruff-0.9.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38840dbcef63948657fa7605ca363194d2fe8c26ce8f9ae12eee7f098c85ac8a"}, + {file = "ruff-0.9.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d56ba06da53536b575fbd2b56517f6f95774ff7be0f62c80b9e67430391eeb36"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7cb2a01da08244c50b20ccfaeb5972e4228c3c3a1989d3ece2bc4b1f996001"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96d5c76358419bc63a671caac70c18732d4fd0341646ecd01641ddda5c39ca0b"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:deb8304636ed394211f3a6d46c0e7d9535b016f53adaa8340139859b2359a070"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df455000bf59e62b3e8c7ba5ed88a4a2bc64896f900f311dc23ff2dc38156440"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de92170dfa50c32a2b8206a647949590e752aca8100a0f6b8cefa02ae29dce80"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d28532d73b1f3f627ba88e1456f50748b37f3a345d2be76e4c653bec6c3e393"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c746d7d1df64f31d90503ece5cc34d7007c06751a7a3bbeee10e5f2463d52d2"}, + {file = "ruff-0.9.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:11417521d6f2d121fda376f0d2169fb529976c544d653d1d6044f4c5562516ee"}, + {file = "ruff-0.9.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b9d71c3879eb32de700f2f6fac3d46566f644a91d3130119a6378f9312a38e1"}, + {file = "ruff-0.9.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2e36c61145e70febcb78483903c43444c6b9d40f6d2f800b5552fec6e4a7bb9a"}, + {file = "ruff-0.9.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2f71d09aeba026c922aa7aa19a08d7bd27c867aedb2f74285a2639644c1c12f5"}, + {file = "ruff-0.9.5-py3-none-win32.whl", hash = "sha256:134f958d52aa6fdec3b294b8ebe2320a950d10c041473c4316d2e7d7c2544723"}, + {file = "ruff-0.9.5-py3-none-win_amd64.whl", hash = "sha256:78cc6067f6d80b6745b67498fb84e87d32c6fc34992b52bffefbdae3442967d6"}, + {file = "ruff-0.9.5-py3-none-win_arm64.whl", hash = "sha256:18a29f1a005bddb229e580795627d297dfa99f16b30c7039e73278cf6b5f9fa9"}, + {file = "ruff-0.9.5.tar.gz", hash = "sha256:11aecd7a633932875ab3cb05a484c99970b9d52606ce9ea912b690b02653d56c"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +summary = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +groups = ["default", "test"] +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.6" +requires_python = ">=3.8" +summary = "A modern CSS selector implementation for Beautiful Soup." +groups = ["default"] +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + +[[package]] +name = "sphinx" +version = "7.4.7" +requires_python = ">=3.9" +summary = "Python documentation generator" +groups = ["default", "test"] +dependencies = [ + "Jinja2>=3.1", + "Pygments>=2.17", + "alabaster~=0.7.14", + "babel>=2.13", + "colorama>=0.4.6; sys_platform == \"win32\"", + "docutils<0.22,>=0.20", + "imagesize>=1.3", + "importlib-metadata>=6.0; python_version < \"3.10\"", + "packaging>=23.0", + "requests>=2.30.0", + "snowballstemmer>=2.2", + "sphinxcontrib-applehelp", + "sphinxcontrib-devhelp", + "sphinxcontrib-htmlhelp>=2.0.0", + "sphinxcontrib-jsmath", + "sphinxcontrib-qthelp", + "sphinxcontrib-serializinghtml>=1.1.9", + "tomli>=2; python_version < \"3.11\"", +] +files = [ + {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, + {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, +] + +[[package]] +name = "sphinx-autoapi" +version = "3.5.0" +requires_python = ">=3.8" +summary = "Sphinx API documentation generator" +groups = ["default"] +dependencies = [ + "Jinja2", + "PyYAML", + "astroid>=2.7; python_version < \"3.12\"", + "astroid>=3; python_version >= \"3.12\"", + "sphinx>=6.1.0", + "stdlib-list; python_version < \"3.10\"", +] +files = [ + {file = "sphinx_autoapi-3.5.0-py3-none-any.whl", hash = "sha256:8676db32dded669dc6be9100696652640dc1e883e45b74710d74eb547a310114"}, + {file = "sphinx_autoapi-3.5.0.tar.gz", hash = "sha256:10dcdf86e078ae1fb144f653341794459e86f5b23cf3e786a735def71f564089"}, +] + +[[package]] +name = "sphinx-book-theme" +version = "1.1.3" +requires_python = ">=3.9" +summary = "A clean book theme for scientific explanations and documentation with Sphinx" +groups = ["default"] +dependencies = [ + "pydata-sphinx-theme>=0.15.2", + "sphinx>=5", +] +files = [ + {file = "sphinx_book_theme-1.1.3-py3-none-any.whl", hash = "sha256:a554a9a7ac3881979a87a2b10f633aa2a5706e72218a10f71be38b3c9e831ae9"}, + {file = "sphinx_book_theme-1.1.3.tar.gz", hash = "sha256:1f25483b1846cb3d353a6bc61b3b45b031f4acf845665d7da90e01ae0aef5b4d"}, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +requires_python = ">=3.5" +summary = "A sphinx extension which renders display math in HTML via JavaScript" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, +] + +[[package]] +name = "tomli" +version = "2.2.1" +requires_python = ">=3.8" +summary = "A lil' TOML parser" +groups = ["default", "lint", "test"] +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +requires_python = ">=3.8" +summary = "Backported and Experimental Type Hints for Python 3.8+" +groups = ["default"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +requires_python = ">=3.8" +summary = "HTTP library with thread-safe connection pooling, file post, and more." +groups = ["default", "test"] +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[[package]] +name = "wasmtime" +version = "25.0.0" +requires_python = ">=3.8" +summary = "A WebAssembly runtime powered by Wasmtime" +groups = ["default"] +dependencies = [ + "importlib-resources>=5.10", +] +files = [ + {file = "wasmtime-25.0.0-py3-none-any.whl", hash = "sha256:22aa59fc6e01deec8a6703046f82466090d5811096a3bb5c169907e36c842af1"}, + {file = "wasmtime-25.0.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:13e9a718e9d580c1738782cc19f4dcb9fb068f7e51778ea621fd664f4433525b"}, + {file = "wasmtime-25.0.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5bdf1214ee3ee78a4a8a92da339f4c4c8c109e65af881b37f4adfc05d02af426"}, + {file = "wasmtime-25.0.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:b4364e14d44e3b7afe6a40bf608e9d0d2c40b09dece441d20f4f6e31906b729c"}, + {file = "wasmtime-25.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:a07445073cf36a6e5d1dc28246a897dcbdaa537ba8be8805be65422ecca297eb"}, + {file = "wasmtime-25.0.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:53d5f614348a28aabdf80ae4f6fdfa803031af1f74ada03826fd4fd43aeee6c8"}, + {file = "wasmtime-25.0.0-py3-none-win_amd64.whl", hash = "sha256:f8a2a213b9179965db2d2eedececd69a37e287e902330509afae51c71a3a6842"}, +] + +[[package]] +name = "yowasp-nextpnr-ecp5" +version = "0.7.0.11.post528.dev0" +summary = "nextpnr-ecp5 FPGA place and route tool" +groups = ["default"] +dependencies = [ + "yowasp-runtime~=1.1", +] +files = [ + {file = "yowasp_nextpnr_ecp5-0.7.0.11.post528.dev0-py3-none-any.whl", hash = "sha256:102d3e81004c402e4422121197fa4eaf5090bd2d6d7e483312f2191233165e66"}, +] + +[[package]] +name = "yowasp-runtime" +version = "1.68" +requires_python = "~=3.8" +summary = "Common runtime for YoWASP packages" +groups = ["default"] +dependencies = [ + "importlib-resources; python_version < \"3.9\"", + "platformdirs<5,>=3.0", + "wasmtime<29,>=21.0.0", +] +files = [ + {file = "yowasp_runtime-1.68-py3-none-any.whl", hash = "sha256:e6a5029dd6bc8608cb6715404a58e212d42046e4e0056da0a56667c1381e028f"}, +] + +[[package]] +name = "yowasp-yosys" +version = "0.48.0.0.post834" +summary = "Yosys Open SYnthesis Suite" +groups = ["default"] +dependencies = [ + "click", + "importlib-resources; python_version < \"3.9\"", + "yowasp-runtime~=1.12", +] +files = [ + {file = "yowasp_yosys-0.48.0.0.post834-py3-none-any.whl", hash = "sha256:6a53cdb48083c7ac88dc9e5312e72e92f5afd69aeea412274505c82f8d8d374c"}, +] + +[[package]] +name = "zipp" +version = "3.21.0" +requires_python = ">=3.9" +summary = "Backport of pathlib-compatible object wrapper for zip files" +groups = ["default"] +files = [ + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, +] diff --git a/pyproject.toml b/pyproject.toml index d17d51c0..8a82f5ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,6 @@ # Project metadata -[tool.pdm.version] -source = "scm" - [project] name = "chipflow-lib" dynamic = ["version"] @@ -16,13 +13,11 @@ authors = [ ] license = {file = "LICENSE.md"} -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "amaranth>=0.5,<0.7", "amaranth-soc @ git+https://github.com/amaranth-lang/amaranth-soc", "amaranth-boards @ git+https://github.com/amaranth-lang/amaranth-boards", - "amaranth-stdio @ git+https://github.com/amaranth-lang/amaranth-stdio", - "amaranth-orchard @ git+https://github.com/ChipFlow/amaranth-orchard", "yowasp-yosys>=0.41.0.0", "yowasp-nextpnr-ecp5>=0.7", "yowasp-runtime", @@ -30,6 +25,10 @@ dependencies = [ "jsonschema>=4.17.3", "doit>=0.36.0", "requests>=2.30.0", + "python-dotenv>=1.0.1", + "pydantic>=2.10.6", + "sphinx-book-theme>=1.1.3", + "sphinx-autoapi>=3.5.0", ] [project.scripts] @@ -43,17 +42,37 @@ build-backend = "pdm.backend" # Development workflow configuration -[tool.pdm.dev-dependencies] -test = [ - "pytest>=7.2.0", - "sphinx>=7.1.2", -] -lint = [ - "pycodestyle>=2.11", -] +[tool.pyright] +diagnosticMode=false +typeCheckingMode = "off" +reportInvalidTypeForm = false +reportMissingImports = false +reportUnboundVariable = false + +[tool.ruff] +include = ["pyproject.toml", "**/*.py", "chipflow.toml"] + +[tool.ruff.lint] +ignore = ['F403', 'F405'] + +[tool.pdm.version] +source = "scm" [tool.pdm.scripts] +test-cov.cmd = "pytest --cov=chipflow_lib --cov-report=html" test.cmd = "pytest" test-docs.cmd = "sphinx-build -b doctest docs/ docs/_build" -lint.cmd = "pycodestyle --config=./.pycodestyle chipflow_lib" -document.cmd = "sphinx-build docs/ docs/_build/ -W --keep-going" +lint.cmd = "ruff check" +docs.cmd = "sphinx-build docs/ docs/_build/ -W --keep-going" + + +[dependency-groups] +lint = [ + "ruff", + "pytest-cov", +] +test = [ + "pytest>=7.2.0", + "sphinx>=7.1.2", + "pytest-cov>=6.0.0", +] diff --git a/tests/fixtures/chipflow-flexic.toml b/tests/fixtures/chipflow-flexic.toml deleted file mode 100644 index 2a1bbaa5..00000000 --- a/tests/fixtures/chipflow-flexic.toml +++ /dev/null @@ -1,35 +0,0 @@ -[chipflow] -project_id = 123 - -[chipflow.steps] -silicon = "thermostat.steps.silicon:MySiliconStep" - -[chipflow.silicon] -process = "customer1" -pad_ring = "cf20" - -[chipflow.silicon.pads] -threshold_0 = { type = "i", loc = "S1" } -threshold_1 = { type = "i", loc = "S2" } -threshold_2 = { type = "i", loc = "S3" } -threshold_3 = { type = "i", loc = "S4" } -threshold_4 = { type = "i", loc = "S5" } -threshold_5 = { type = "i", loc = "S6" } -threshold_6 = { type = "i", loc = "S7" } - -sclk = { type = "o", loc = "W3" } -sdi = { type = "i", loc = "W2" } -sdo = { type = "o", loc = "W1" } - -cool = { type = "o", loc = "E1" } -heat = { type = "o", loc = "E3" } - -ssn = { type = "o", loc = "N2" } -sys_clk = { type = "clk", loc = "N3" } -sys_rstn = { type = "i", loc = "N4" } - -[chipflow.silicon.power] -vss = { loc = "N1" } -vssio = { loc = "N5" } -vddio = { loc = "N6" } -vdd = { loc = "N7" } diff --git a/tests/fixtures/mock.toml b/tests/fixtures/mock.toml new file mode 100644 index 00000000..72e319e7 --- /dev/null +++ b/tests/fixtures/mock.toml @@ -0,0 +1,25 @@ +[chipflow] +project_name = "proj-name" + +[chipflow.steps] +silicon = "chipflow_lib.steps.silicon:SiliconStep" + +[chipflow.silicon] +process = "ihp_sg13g2" +package = "pga144" + +[chipflow.clocks] +default = 'sys_clk' + +[chipflow.resets] +default = 'sys_rst_n' + +[chipflow.silicon.pads] +sys_clk = { type = "clk", loc = "N3" } +sys_rst_n = { type = "i", loc = "N4" } + +[chipflow.silicon.power] +vss = { loc = "N1" } +vssio = { loc = "N5" } +vddio = { loc = "N6" } +vdd = { loc = "N7" } diff --git a/tests/fixtures/mock_top.py b/tests/fixtures/mock_top.py new file mode 100644 index 00000000..185cae41 --- /dev/null +++ b/tests/fixtures/mock_top.py @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: BSD-2-Clause +from amaranth import Module +from amaranth.lib import wiring +from amaranth.lib.wiring import In, Out + +from chipflow_lib.platforms import InputPinSignature, OutputPinSignature, BidirPinSignature + +__all__ = ["MockTop"] + +TestSignature1 = wiring.Signature({ + "a": In(InputPinSignature(1)), + "b": In(InputPinSignature(5)), + "c": Out(OutputPinSignature(1)), + "d": Out(OutputPinSignature(10)), + "e": In(BidirPinSignature(1)), + "f": In(BidirPinSignature(7)), +}) + +TestSignature2 = wiring.Signature({ + "a": Out(OutputPinSignature(1)), + "b": Out(OutputPinSignature(5)), + "c": In(InputPinSignature(1)), + "d": In(InputPinSignature(10)), + "e": Out(BidirPinSignature(1)), + "f": Out(BidirPinSignature(7)), +}) + + +# --------- + +class MockTop(wiring.Component): + def __init__(self): + # Top level interfaces + + interfaces = { + "test1" : Out(TestSignature1), + "test2": Out(TestSignature2) + } + + super().__init__(interfaces) + + def elaborate(self, platform): + m = Module() + for inpin, outpin in zip(self.test1.members, self.test2.members): + m.d.comb += inpin.eq(outpin) + + return m \ No newline at end of file diff --git a/tests/test_silicon_platform.py b/tests/test_silicon_platform.py index 495bbe2c..9795e0e0 100644 --- a/tests/test_silicon_platform.py +++ b/tests/test_silicon_platform.py @@ -2,10 +2,11 @@ # SPDX-License-Identifier: BSD-2-Clause import os - import unittest + +import tomli + from amaranth import * -from amaranth.hdl import Fragment from amaranth.hdl._ir import Design from chipflow_lib import ChipFlowError @@ -15,19 +16,23 @@ class SiliconPlatformTestCase(unittest.TestCase): def setUp(self): os.environ["CHIPFLOW_ROOT"] = os.path.dirname(os.path.dirname(__file__)) + current_dir = os.path.dirname(__file__) + customer_config = f"{current_dir}/fixtures/mock.toml" + with open(customer_config, "rb") as f: + self.config = tomli.load(f) def test_sync_domain_works(self): m = Module() m.domains += ClockDomain("sync") - fragment = SiliconPlatform(pads={})._prepare(m) + fragment = SiliconPlatform(self.config)._prepare(m) self.assertIsInstance(fragment, Design) def test_subfragment_works(self): m = Module() m.submodules += Module() - fragment = SiliconPlatform(pads={})._prepare(m) + fragment = SiliconPlatform(self.config)._prepare(m) self.assertIsInstance(fragment, Design) def test_wrong_clock_domain_name(self): @@ -37,4 +42,4 @@ def test_wrong_clock_domain_name(self): with self.assertRaisesRegex( ChipFlowError, r"^Only a single clock domain, called 'sync', may be used$"): - SiliconPlatform(pads={}).build(m) + SiliconPlatform(self.config).build(m) diff --git a/tests/test_silicon_step.py b/tests/test_silicon_step.py index ef8fce24..0df19433 100644 --- a/tests/test_silicon_step.py +++ b/tests/test_silicon_step.py @@ -1,11 +1,11 @@ # SPDX-License-Identifier: BSD-2-Clause - import io import json import os import tomli import unittest from contextlib import redirect_stdout +from pprint import pformat from unittest.mock import patch from chipflow_lib.steps.silicon import SiliconStep @@ -29,7 +29,7 @@ def json(self): "msg": "msg", "url": "https://example.com/build-url/", "name": "name", - "id": 123, + "id": "proj-name", }, 200) return MockResponse(None, 404) @@ -41,9 +41,10 @@ def setUp(self): os.environ["CHIPFLOW_API_KEY_ID"] = "keyid" os.environ["CHIPFLOW_API_KEY_SECRET"] = "keysecret" + @patch('dotenv.load_dotenv') @patch('requests.post', side_effect=mocked_requests_post) - def test_submit_happy_path(self, mock_requests_post): - customer_config = f"{current_dir}/fixtures/chipflow-flexic.toml" + def test_submit_happy_path(self, mock_requests_post, mock_dotenv): + customer_config = f"{current_dir}/fixtures/mock.toml" with open(customer_config, "rb") as f: config_dict = tomli.load(f) @@ -53,7 +54,7 @@ def test_submit_happy_path(self, mock_requests_post): with redirect_stdout(f): silicon_step.submit(current_dir + "/fixtures/mock.rtlil") output = f.getvalue() - assert 'msg (#123: name); https://example.com/build-url/' in output, "The printed output is correct." + assert 'msg (#proj-name: name); https://example.com/build-url/' in output, "The printed output is correct." args = mock_requests_post.call_args_list[0][0] kwargs = mock_requests_post.call_args_list[0][1] @@ -63,7 +64,7 @@ def test_submit_happy_path(self, mock_requests_post): rtlil = files["rtlil"].read() assert args[0] == 'https://app.chipflow-infra.com/api/builds' assert kwargs["auth"] == ("keyid", "keysecret") - assert data["projectId"] == 123 + assert data["projectId"] == 'proj-name' assert isinstance(data["name"], str), "Name is a string" assert list(config["dependency_versions"]) == [ "python", @@ -73,10 +74,10 @@ def test_submit_happy_path(self, mock_requests_post): "amaranth-orchard", "amaranth-vexriscv", ], "We have entries for the the dependency versions" + print(pformat(config)) assert config["silicon"] == { - 'process': 'customer1', - 'pad_ring': - 'cf20', + 'process': 'ihp_sg13g2', + 'package': 'pga144', 'pads': {}, 'power': { 'vss': {'loc': 'N1'}, @@ -86,3 +87,5 @@ def test_submit_happy_path(self, mock_requests_post): } } assert rtlil == b"fake-rtlil", "The RTL file was passed through." + + assert mock_dotenv.called diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000..e0f77280 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: BSD-2-Clause +import itertools +import logging +import pytest #noqa + +from pprint import pformat + +from amaranth.lib import io + +from chipflow_lib.platforms.utils import PinSignature, OutputPinSignature, InputPinSignature, BidirPinSignature, _PinAnnotation, _PinAnnotationModel +from chipflow_lib.platforms.utils import PinList, _group_consecutive_items,_find_contiguous_sequence, _Side + + +logger = logging.getLogger(__name__) + + +def gen_quad_pins(width, height) -> PinList: + return sorted( + [e for e in itertools.product((_Side.N, _Side.S), range(width))] + + [e for e in itertools.product((_Side.W, _Side.E), range(height))] + ) + + +def test_group_consecutive_items_null(): + ordering = gen_quad_pins(50,60) + pins = ordering.copy() + groups = _group_consecutive_items(pins,pins) + assert len(groups.keys()) == 1 + assert len(ordering) in groups.keys() + +def test_group_consecutive_items_nonconsecutive(): + ordering = gen_quad_pins(50,60) + pins = ordering[0:6] + ordering[7:70] + ordering[71:180] + ordering[181:] + logger.debug(f"{ordering} {pins}") + groups = _group_consecutive_items(ordering,pins) + logger.debug(f"\n{pformat(groups)}") + assert len(ordering) == 50*2 + 60*2 + assert len(groups.keys()) == 4 + assert sum(groups.keys()) == len(ordering) - 3 + assert 6 in groups.keys() + assert 70 - 7 in groups.keys() + assert 180 - 71 in groups.keys() + assert len(ordering) -181 in groups.keys() + +def test_find_contiguous_sequence(): + ordering = gen_quad_pins(50,60) + pins = ordering[0:6] + ordering[7:70] + ordering[71:180] + ordering[181:] + seq = _find_contiguous_sequence(ordering, pins, 120) + logger.debug(f"\n{pformat(seq)}") + logger.debug(f"{ordering[71:180] + ordering[181:191]}") + assert len(seq) == 120 + assert seq == ordering[71:180] + ordering[181:192] + + +def test_pin_signature(): + sig_bidir = PinSignature(io.Direction.Bidir, width=8) + assert isinstance(sig_bidir, PinSignature) + assert sig_bidir._direction == io.Direction.Bidir + assert sig_bidir._width == 8 + assert "o" in sig_bidir.members + assert "oe" in sig_bidir.members + assert "i" in sig_bidir.members + + sig_output = OutputPinSignature(width=4) + assert isinstance(sig_output, PinSignature) + assert sig_output._direction == io.Direction.Output + assert sig_output._width == 4 + assert "o" in sig_output.members + assert "oe" not in sig_output.members + assert "i" not in sig_output.members + + sig_input = InputPinSignature(width=2) + assert isinstance(sig_input, PinSignature) + assert sig_input._direction == io.Direction.Input + assert sig_input._width == 2 + assert "o" not in sig_input.members + assert "oe" not in sig_output.members + assert "i" in sig_input.members + + sig_bidir_fn = BidirPinSignature(width=1) + assert isinstance(sig_bidir_fn, PinSignature) + assert sig_bidir_fn._direction == io.Direction.Bidir + assert sig_bidir_fn._width == 1 + assert "o" in sig_bidir_fn.members + assert "oe" in sig_bidir_fn.members + assert "i" in sig_bidir_fn.members + +def test_pin_annotation_model(): + model = _PinAnnotationModel(direction=io.Direction.Output, width=32) + assert model.direction == "o" + assert model.width == 32 + +def test_pin_annotation(): + annotation = _PinAnnotation(direction=io.Direction.Input, width=16) + assert isinstance(annotation, _PinAnnotation) + assert annotation.model.direction == "i" + assert annotation.model.width == 16 + +def test_pin_annotation_as_json(): + annotation = _PinAnnotation(direction=io.Direction.Bidir, width=8) + json_output = annotation.as_json() + print(f"json_output: {json_output}") # Debug print using print() + assert isinstance(json_output, dict) + assert json_output["direction"] == "io" + assert json_output["width"] == 8 \ No newline at end of file