From b84ff10adbb92aeef2555275c27c4600433d9907 Mon Sep 17 00:00:00 2001 From: Mikael Brockman Date: Wed, 27 Nov 2024 00:48:17 +0200 Subject: [PATCH] refactor: Rename and reorganize modules for improved clarity and consistency --- .gitignore | 5 +- bubble/boot.py | 285 ++++++++++++++++++ bubble/bubbleboot.py | 10 +- bubble/{capabilities.py => caps.py} | 6 +- bubble/eyereason.py | 86 ------ bubble/{rdfjson.py => json.py} | 4 +- bubble/{macsysinfo.py => macs.py} | 0 bubble/main.py | 4 +- bubble/{gensym.py => mint.py} | 2 +- bubble/{ns.py => prfx.py} | 0 bubble/{bubblerepo.py => repo.py} | 10 +- bubble/{sysinfo.py => stat.py} | 8 +- bubble/test_vars.py | 0 .../{test_capabilities.py => test_caps.py} | 8 +- .../tests/{test_jsonrdf.py => test_json.py} | 8 +- bubble/tests/{test_gensym.py => test_mint.py} | 2 +- .../tests/{test_sysinfo.py => test_stat.py} | 4 +- bubble/{rdfutil.py => util.py} | 6 +- bubble/{graphvar.py => vars.py} | 4 +- bubble/{wikidata.py => wiki.py} | 0 pyproject.toml | 4 +- uv.lock | 30 ++ 22 files changed, 358 insertions(+), 128 deletions(-) create mode 100644 bubble/boot.py rename bubble/{capabilities.py => caps.py} (97%) delete mode 100644 bubble/eyereason.py rename bubble/{rdfjson.py => json.py} (95%) rename bubble/{macsysinfo.py => macs.py} (100%) rename bubble/{gensym.py => mint.py} (98%) rename bubble/{ns.py => prfx.py} (100%) rename bubble/{bubblerepo.py => repo.py} (95%) rename bubble/{sysinfo.py => stat.py} (94%) create mode 100644 bubble/test_vars.py rename bubble/tests/{test_capabilities.py => test_caps.py} (97%) rename bubble/tests/{test_jsonrdf.py => test_json.py} (95%) rename bubble/tests/{test_gensym.py => test_mint.py} (98%) rename bubble/tests/{test_sysinfo.py => test_stat.py} (93%) rename bubble/{rdfutil.py => util.py} (96%) rename bubble/{graphvar.py => vars.py} (95%) rename bubble/{wikidata.py => wiki.py} (100%) diff --git a/.gitignore b/.gitignore index e90b561..93d3193 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ priv # put secrets here /**/priv .DS_Store build -**/build \ No newline at end of file +**/build +/htmlcov +.coverage + diff --git a/bubble/boot.py b/bubble/boot.py new file mode 100644 index 0000000..cd2481c --- /dev/null +++ b/bubble/boot.py @@ -0,0 +1,285 @@ +"""Bubble initialization module. + +This module handles the creation of new bubbles, which are versioned RDF graphs. +It sets up the initial graph structure with system information, user details, +and basic bubble metadata. The created bubble includes: + +- System information (OS, CPU, RAM, filesystem details) +- User account information +- Basic bubble structure with initial steps +- Root surface file + +The module uses RDF and the Notation3 format to represent all data. +""" + +from bubble.mint import fresh_iri +from bubble.vars import bind_prefixes, langstr, quote, using_graph +from bubble.prfx import AS, NT, SWA, UUID +from bubble.util import new +from bubble.stat import gather_system_info + + +from rdflib import OWL, RDFS, Graph, Literal +from rdflib.graph import _SubjectType +from trio import Path + + +import getpass + + +async def describe_new_bubble(path: Path) -> _SubjectType: + info = await gather_system_info() + return await construct_bubble_graph(path, info) + + +async def construct_bubble_graph(path, info): + surface = fresh_iri() + step = fresh_iri() + head = fresh_iri() + + with using_graph(Graph()) as g: + bind_prefixes() + + machine = SWA[info["machine_id"]] + + filesystem = describe_filesystem(info) + + home_dir = await describe_home_directory(info, filesystem) + bubble = describe_bubble(step, info) + user = describe_user_account(info, home_dir) + + describe_repository(path, filesystem, home_dir, bubble) + describe_machine(info, machine, filesystem, user) + describe_creation_event(user, bubble, path, info) + describe_steps(bubble, step, head, path) + describe_surface_addition(user, bubble, surface, path) + + g.serialize(destination=path / "root.n3", format="n3") + + return bubble + + +def describe_user_account(info, home_dir): + return new( + NT.Account, + { + NT.gid: info["user_info"].pw_gid, + NT.homeDirectory: home_dir, + NT.owner: new( + AS.Person, + {NT.name: info["person_name"]}, + ), + NT.uid: info["user_info"].pw_uid, + NT.username: getpass.getuser(), + RDFS.label: langstr( + f"user account for {info['person_name']}" + ), + }, + ) + + +def describe_repository(path, filesystem, home_dir, bubble): + new( + NT.Repository, + { + NT.tracks: bubble, + NT.worktree: new( + NT.Directory, + { + NT.filesystem: filesystem, + NT.parent: home_dir, + NT.path: path, + RDFS.label: langstr( + f"worktree directory at {path}", + ), + }, + ), + RDFS.label: langstr( + f"bubble repository at {path}", + ), + }, + ) + + +def describe_bubble(step, info): + return new( + NT.Bubble, + { + RDFS.label: langstr( + f"a bubble for {info['person_name']}", + ), + NT.head: step, + }, + ) + + +async def describe_home_directory(info, filesystem): + return new( + NT.Directory, + { + NT.filesystem: filesystem, + NT.path: await Path.home(), + RDFS.label: langstr( + f"home directory of {info['person_name']}", + ), + }, + ) + + +def describe_filesystem(info): + return new( + NT.Filesystem, + { + NT.byteSize: Literal(info["disk_info"]["Size"]), + OWL.sameAs: UUID[info["disk_uuid"]], + RDFS.label: langstr( + f"disk {info['disk_info']['VolumeName']}", + ), + }, + ) + + +def describe_machine(info, machine, filesystem, user): + new( + NT.ComputerMachine, + { + RDFS.label: langstr( + f"{info['person_name']}'s {info['system_type']} computer", + ), + NT.hosts: [ + describe_posixenv(info, filesystem, user), + describe_os(info), + ], + NT.part: [ + describe_cpu(info), + describe_ram(info), + ], + NT.serialNumber: info["computer_serial"], + }, + subject=machine, + ) + + +def describe_ram(info): + return new( + NT.RandomAccessMemory, + { + NT.byteSize: Literal(info["byte_size"]), + NT.gigabyteSize: info["gigabyte_size"], + RDFS.label: langstr( + f"the RAM of {info['person_name']}'s {info['system_type']} computer", + ), + }, + ) + + +def describe_cpu(info): + return new( + NT.CentralProcessingUnit, + { + NT.architecture: info["architecture"], + RDFS.label: langstr( + f"the CPU of {info['person_name']}'s {info['system_type']} computer", + ), + }, + ) + + +def describe_os(info): + return new( + NT.OperatingSystem, + { + NT.type: info["system_type"], + NT.version: info["system_version"], + RDFS.label: langstr( + f"the {info['system_type']} operating system installed on {info['hostname']}", + ), + }, + ) + + +def describe_posixenv(info, filesystem, user): + return new( + NT.PosixEnvironment, + { + NT.account: user, + NT.filesystem: filesystem, + NT.hostname: info["hostname"], + RDFS.label: langstr( + f"POSIX environment on {info['hostname']}", + ), + }, + ) + + +def describe_creation_event(user, bubble, path, info): + new( + AS.Create, + { + AS.actor: user, + AS.object: bubble, + AS.published: info["now"], + RDFS.label: langstr( + f"creation of the bubble at {path}", + ), + }, + ) + + +def describe_steps(bubble, step, head, path): + describe_initial_step(bubble, step, path) + describe_next_step(bubble, step, head, path) + + +def describe_next_step(bubble, step, head, path): + new( + NT.Step, + { + NT.supposes: quote([(bubble, NT.head, head)]), + NT.succeeds: step, + RDFS.label: langstr( + f"the second step of the bubble at {path}", + ), + }, + subject=head, + ) + + +def describe_initial_step(bubble, step, path): + new( + NT.Step, + { + NT.supposes: quote([(bubble, NT.head, step)]), + RDFS.label: langstr( + f"the first step of the bubble at {path}", + ), + }, + subject=step, + ) + + +def describe_surface_addition(user, bubble, surface, path): + new( + AS.Add, + { + AS.actor: user, + AS.object: describe_root_surface(bubble, surface, path), + AS.target: bubble, + RDFS.label: langstr( + f"addition of the root surface to the bubble at {path}", + ), + }, + ) + + +def describe_root_surface(bubble, surface, path): + return new( + NT.Surface, + { + NT.partOf: bubble, + RDFS.label: langstr( + f"the root surface of the bubble at {path}", + ), + }, + subject=surface, + ) diff --git a/bubble/bubbleboot.py b/bubble/bubbleboot.py index 02243bf..cd2481c 100644 --- a/bubble/bubbleboot.py +++ b/bubble/bubbleboot.py @@ -12,11 +12,11 @@ The module uses RDF and the Notation3 format to represent all data. """ -from bubble.gensym import fresh_iri -from bubble.graphvar import bind_prefixes, langstr, quote, using_graph -from bubble.ns import AS, NT, SWA, UUID -from bubble.rdfutil import new -from bubble.sysinfo import gather_system_info +from bubble.mint import fresh_iri +from bubble.vars import bind_prefixes, langstr, quote, using_graph +from bubble.prfx import AS, NT, SWA, UUID +from bubble.util import new +from bubble.stat import gather_system_info from rdflib import OWL, RDFS, Graph, Literal diff --git a/bubble/capabilities.py b/bubble/caps.py similarity index 97% rename from bubble/capabilities.py rename to bubble/caps.py index b5e8beb..749b659 100644 --- a/bubble/capabilities.py +++ b/bubble/caps.py @@ -13,9 +13,9 @@ from rdflib.query import ResultRow from rich.console import Console -from bubble.ns import NT -from bubble.rdfjson import json_from_rdf -from bubble.rdfutil import new, select_one_row +from bubble.prfx import NT +from bubble.json import json_from_rdf +from bubble.util import new, select_one_row console = Console() diff --git a/bubble/eyereason.py b/bubble/eyereason.py deleted file mode 100644 index 4d16d1e..0000000 --- a/bubble/eyereason.py +++ /dev/null @@ -1,86 +0,0 @@ -"""N3 processor for handling N3 files and invocations.""" - -from glob import glob -from typing import Optional -from pathlib import Path - -import trio - -from rich import pretty -from rdflib import URIRef -from rdflib.graph import _SubjectType -from rich.console import Console - -from bubble.rdfutil import select_rows -from bubble.capabilities import InvocationContext, capability_map - -console = Console() -pretty.install() - -CORE_RULES_DIR = Path(__file__).parent / "rules" - - -class StepExecution: - """Engine for processing N3 files and applying rules""" - - def __init__(self, base: str, step: Optional[str] = None): - self.step = step - self.base = base - - async def reason(self) -> None: - """Run the EYE reasoner on N3 files and update the processor's graph""" - from bubble.rdfutil import reason - - if not self.step: - raise ValueError("No step file provided") - - # Get all rule files for reasoning - rule_files = glob(str(CORE_RULES_DIR / "*.n3")) - - # If step is a directory, get all n3 files, otherwise use the - # single file - step_files = ( - glob(str(Path(self.step) / "*.n3")) - if Path(self.step).is_dir() - else [self.step] - ) - - # Combine all files for reasoning - all_files = step_files + rule_files - self.graph = await reason(all_files) - - async def process_invocations(self, step: _SubjectType) -> None: - rows = select_rows( - """ - SELECT ?invocation ?capability_type - WHERE { - ?invocation a nt:Invocation . - ?step nt:invokes ?invocation . - ?invocation nt:invokes ?target . - ?target a ?capability_type . - } - """, - {"step": step}, - ) - - console.print(f"Processing {len(rows)} invocations") - - async with trio.open_nursery() as nursery: - for invocation, capability_type in rows: - cap = capability_map[capability_type] - ctx = InvocationContext(invocation) - nursery.start_soon(cap, ctx) - - async def process(self) -> None: - """Main processing method""" - try: - step = URIRef(f"{self.base}#step") - console.print(f"Processing step: {step}") - await self.process_invocations(step) - - except* Exception as e: - for error in e.exceptions: - console.print( - f"[red]Error processing N3:[/red] {str(error)}" - ) - raise error diff --git a/bubble/rdfjson.py b/bubble/json.py similarity index 95% rename from bubble/rdfjson.py rename to bubble/json.py index 9d424f6..22bc6c5 100644 --- a/bubble/rdfjson.py +++ b/bubble/json.py @@ -3,8 +3,8 @@ from rdflib import Literal, IdentifiedNode from rdflib.graph import _SubjectType -from bubble.ns import JSON -from bubble.rdfutil import O, S, new, select_rows +from bubble.prfx import JSON +from bubble.util import O, S, new, select_rows def json_from_rdf(node: _SubjectType) -> dict: diff --git a/bubble/macsysinfo.py b/bubble/macs.py similarity index 100% rename from bubble/macsysinfo.py rename to bubble/macs.py diff --git a/bubble/main.py b/bubble/main.py index 31bfeb0..717ea72 100644 --- a/bubble/main.py +++ b/bubble/main.py @@ -11,8 +11,8 @@ from rich.logging import RichHandler from anthropic.types import MessageParam -from bubble.gensym import Mint -from bubble.bubblerepo import BubbleRepo +from bubble.mint import Mint +from bubble.repo import BubbleRepo FORMAT = "%(message)s" logging.basicConfig( diff --git a/bubble/gensym.py b/bubble/mint.py similarity index 98% rename from bubble/gensym.py rename to bubble/mint.py index 0f67219..11735d4 100644 --- a/bubble/gensym.py +++ b/bubble/mint.py @@ -5,7 +5,7 @@ from xid import XID from rdflib import BNode, URIRef, Namespace -from bubble.ns import SWA +from bubble.prfx import SWA class Mint: diff --git a/bubble/ns.py b/bubble/prfx.py similarity index 100% rename from bubble/ns.py rename to bubble/prfx.py diff --git a/bubble/bubblerepo.py b/bubble/repo.py similarity index 95% rename from bubble/bubblerepo.py rename to bubble/repo.py index 904b76c..8fa38bb 100644 --- a/bubble/bubblerepo.py +++ b/bubble/repo.py @@ -21,10 +21,10 @@ ) from rdflib.graph import _SubjectType -from bubble.bubbleboot import describe_new_bubble -from bubble.gensym import Mint, mintvar -from bubble.ns import NT -from bubble.rdfutil import get_single_subject +from bubble.boot import describe_new_bubble +from bubble.mint import Mint, mintvar +from bubble.prfx import NT +from bubble.util import get_single_subject logger = logging.getLogger(__name__) @@ -93,7 +93,7 @@ async def load_rules(self) -> None: async def reason(self) -> Graph: """Reason over the graph""" - from bubble.rdfutil import reason + from bubble.util import reason tmpfile = Path(tempfile.gettempdir()) / "bubble.n3" self.graph.serialize(destination=tmpfile, format="n3") diff --git a/bubble/sysinfo.py b/bubble/stat.py similarity index 94% rename from bubble/sysinfo.py rename to bubble/stat.py index 23a7c0f..de56d8a 100644 --- a/bubble/sysinfo.py +++ b/bubble/stat.py @@ -6,15 +6,15 @@ import psutil from rdflib import XSD, Literal -from bubble.macsysinfo import computer_serial_number, get_disk_info -from bubble.ns import NT -from bubble.gensym import mintvar +from bubble.macs import computer_serial_number, get_disk_info +from bubble.prfx import NT +from bubble.mint import mintvar from typing import TypedDict from pwd import struct_passwd from rdflib import URIRef -from bubble.macsysinfo import DiskInfo +from bubble.macs import DiskInfo class SystemInfo(TypedDict): diff --git a/bubble/test_vars.py b/bubble/test_vars.py new file mode 100644 index 0000000..e69de29 diff --git a/bubble/tests/test_capabilities.py b/bubble/tests/test_caps.py similarity index 97% rename from bubble/tests/test_capabilities.py rename to bubble/tests/test_caps.py index 7088b2b..7439177 100644 --- a/bubble/tests/test_capabilities.py +++ b/bubble/tests/test_caps.py @@ -6,10 +6,10 @@ from rdflib import Graph, URIRef, Literal from pytest_httpx import HTTPXMock -from bubble.graphvar import using_graph -from bubble.ns import NT -from bubble.rdfutil import turtle -from bubble.capabilities import ( +from bubble.vars import using_graph +from bubble.prfx import NT +from bubble.util import turtle +from bubble.caps import ( InvocationContext, FileResult, http_client, diff --git a/bubble/tests/test_jsonrdf.py b/bubble/tests/test_json.py similarity index 95% rename from bubble/tests/test_jsonrdf.py rename to bubble/tests/test_json.py index 4bae714..36ee00f 100644 --- a/bubble/tests/test_jsonrdf.py +++ b/bubble/tests/test_json.py @@ -2,14 +2,14 @@ from rdflib import Graph, Literal, Variable -from bubble.graphvar import using_graph -from bubble.ns import JSON -from bubble.rdfjson import ( +from bubble.vars import using_graph +from bubble.prfx import JSON +from bubble.json import ( rdf_from_json, json_from_rdf, convert_json_value, ) -from bubble.rdfutil import new, select_rows +from bubble.util import new, select_rows @pytest.fixture diff --git a/bubble/tests/test_gensym.py b/bubble/tests/test_mint.py similarity index 98% rename from bubble/tests/test_gensym.py rename to bubble/tests/test_mint.py index 79bd9dd..faa4063 100644 --- a/bubble/tests/test_gensym.py +++ b/bubble/tests/test_mint.py @@ -4,7 +4,7 @@ from rdflib import Namespace -from bubble.gensym import Mint +from bubble.mint import Mint @pytest.fixture diff --git a/bubble/tests/test_sysinfo.py b/bubble/tests/test_stat.py similarity index 93% rename from bubble/tests/test_sysinfo.py rename to bubble/tests/test_stat.py index 68a0ff1..d4dcab5 100644 --- a/bubble/tests/test_sysinfo.py +++ b/bubble/tests/test_stat.py @@ -1,8 +1,8 @@ import platform import pytest -from bubble.sysinfo import gather_system_info -from bubble.ns import NT +from bubble.stat import gather_system_info +from bubble.prfx import NT @pytest.mark.skipif( diff --git a/bubble/rdfutil.py b/bubble/util.py similarity index 96% rename from bubble/rdfutil.py rename to bubble/util.py index 92f0492..91ed509 100644 --- a/bubble/rdfutil.py +++ b/bubble/util.py @@ -8,9 +8,9 @@ from rdflib.graph import _ObjectType, _SubjectType, _PredicateType from rdflib.query import ResultRow -from bubble.gensym import fresh_uri -from bubble.graphvar import graphvar, using_graph -from bubble.ns import NT, SWA, JSON +from bubble.mint import fresh_uri +from bubble.vars import graphvar, using_graph +from bubble.prfx import NT, SWA, JSON S = _SubjectType diff --git a/bubble/graphvar.py b/bubble/vars.py similarity index 95% rename from bubble/graphvar.py rename to bubble/vars.py index abcee8b..6af5993 100644 --- a/bubble/graphvar.py +++ b/bubble/vars.py @@ -7,11 +7,11 @@ from contextvars import ContextVar -from bubble.gensym import fresh_iri +from bubble.mint import fresh_iri from rdflib.graph import _TripleType, QuotedGraph -from bubble.ns import AS, NT, SWA +from bubble.prfx import AS, NT, SWA graphvar = ContextVar("graph", default=Graph()) diff --git a/bubble/wikidata.py b/bubble/wiki.py similarity index 100% rename from bubble/wikidata.py rename to bubble/wiki.py diff --git a/pyproject.toml b/pyproject.toml index 11c7c73..d842e41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ dependencies = [ # Pytest plugin for mocking httpx requests "pytest-httpx>=0.34.0", "autopep8>=2.3.1", + "coverage>=7.6.8", ] [tool.uv.sources] @@ -68,9 +69,6 @@ include = ["bubble"] [project.scripts] bubble = "bubble.main:main" -# run this to wrap comments; ruff can't do that -wrapcomments = "autopep8 --in-place --max-line-length 72 --select E501 --aggressive bubble/**.py" - [tool.ruff] line-length = 72 # slim fit target-version = "py313" # I'm not interested in old Python versions diff --git a/uv.lock b/uv.lock index 899b675..c3cc122 100644 --- a/uv.lock +++ b/uv.lock @@ -69,6 +69,7 @@ source = { editable = "." } dependencies = [ { name = "anthropic" }, { name = "autopep8" }, + { name = "coverage" }, { name = "httpx" }, { name = "mock" }, { name = "openai" }, @@ -100,6 +101,7 @@ dev = [ requires-dist = [ { name = "anthropic", specifier = "==0.39.*" }, { name = "autopep8", specifier = ">=2.3.1" }, + { name = "coverage", specifier = ">=7.6.8" }, { name = "httpx", specifier = "==0.*" }, { name = "mock", specifier = ">=5.1.0" }, { name = "openai", specifier = "==1.55.*" }, @@ -179,6 +181,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "coverage" +version = "7.6.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/75/aecfd0a3adbec6e45753976bc2a9fed62b42cea9a206d10fd29244a77953/coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc", size = 801425 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/84/6f0ccf94a098ac3d6d6f236bd3905eeac049a9e0efcd9a63d4feca37ac4b/coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb", size = 207313 }, + { url = "https://files.pythonhosted.org/packages/db/2b/e3b3a3a12ebec738c545897ac9f314620470fcbc368cdac88cf14974ba20/coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63", size = 207574 }, + { url = "https://files.pythonhosted.org/packages/db/c0/5bf95d42b6a8d21dfce5025ce187f15db57d6460a59b67a95fe8728162f1/coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365", size = 240090 }, + { url = "https://files.pythonhosted.org/packages/57/b8/d6fd17d1a8e2b0e1a4e8b9cb1f0f261afd422570735899759c0584236916/coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002", size = 237237 }, + { url = "https://files.pythonhosted.org/packages/d4/e4/a91e9bb46809c8b63e68fc5db5c4d567d3423b6691d049a4f950e38fbe9d/coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3", size = 239225 }, + { url = "https://files.pythonhosted.org/packages/31/9c/9b99b0591ec4555b7292d271e005f27b465388ce166056c435b288db6a69/coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022", size = 238888 }, + { url = "https://files.pythonhosted.org/packages/a6/85/285c2df9a04bc7c31f21fd9d4a24d19e040ec5e2ff06e572af1f6514c9e7/coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e", size = 236974 }, + { url = "https://files.pythonhosted.org/packages/cb/a1/95ec8522206f76cdca033bf8bb61fff56429fb414835fc4d34651dfd29fc/coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b", size = 238815 }, + { url = "https://files.pythonhosted.org/packages/8d/ac/687e9ba5e6d0979e9dab5c02e01c4f24ac58260ef82d88d3b433b3f84f1e/coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", size = 209957 }, + { url = "https://files.pythonhosted.org/packages/2f/a3/b61cc8e3fcf075293fb0f3dee405748453c5ba28ac02ceb4a87f52bdb105/coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", size = 210711 }, + { url = "https://files.pythonhosted.org/packages/ee/4b/891c8b9acf1b62c85e4a71dac142ab9284e8347409b7355de02e3f38306f/coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d", size = 208053 }, + { url = "https://files.pythonhosted.org/packages/18/a9/9e330409b291cc002723d339346452800e78df1ce50774ca439ade1d374f/coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451", size = 208329 }, + { url = "https://files.pythonhosted.org/packages/9c/0d/33635fd429f6589c6e1cdfc7bf581aefe4c1792fbff06383f9d37f59db60/coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764", size = 251052 }, + { url = "https://files.pythonhosted.org/packages/23/32/8a08da0e46f3830bbb9a5b40614241b2e700f27a9c2889f53122486443ed/coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf", size = 246765 }, + { url = "https://files.pythonhosted.org/packages/56/3f/3b86303d2c14350fdb1c6c4dbf9bc76000af2382f42ca1d4d99c6317666e/coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5", size = 249125 }, + { url = "https://files.pythonhosted.org/packages/36/cb/c4f081b9023f9fd8646dbc4ef77be0df090263e8f66f4ea47681e0dc2cff/coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4", size = 248615 }, + { url = "https://files.pythonhosted.org/packages/32/ee/53bdbf67760928c44b57b2c28a8c0a4bf544f85a9ee129a63ba5c78fdee4/coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83", size = 246507 }, + { url = "https://files.pythonhosted.org/packages/57/49/5a57910bd0af6d8e802b4ca65292576d19b54b49f81577fd898505dee075/coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b", size = 247785 }, + { url = "https://files.pythonhosted.org/packages/bd/37/e450c9f6b297c79bb9858407396ed3e084dcc22990dd110ab01d5ceb9770/coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", size = 210605 }, + { url = "https://files.pythonhosted.org/packages/44/79/7d0c7dd237c6905018e2936cd1055fe1d42e7eba2ebab3c00f4aad2a27d7/coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", size = 211777 }, +] + [[package]] name = "dataproperty" version = "1.0.1"