From d5e027d5813af33e90c9e57a0ddd023154e25c82 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 5 Sep 2025 14:31:35 +0200 Subject: [PATCH 001/171] Add initial file layout for SALT models --- src/aeonlib/salt/__init__.py | 0 src/aeonlib/salt/models/__init__.py | 0 tests/salt/test_models.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/aeonlib/salt/__init__.py create mode 100644 src/aeonlib/salt/models/__init__.py create mode 100644 tests/salt/test_models.py diff --git a/src/aeonlib/salt/__init__.py b/src/aeonlib/salt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/salt/test_models.py b/tests/salt/test_models.py new file mode 100644 index 0000000..e69de29 From 9f47c177818f11c58908547b8dc4ada473e394be Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 5 Sep 2025 16:06:10 +0200 Subject: [PATCH 002/171] Add the observation request model --- src/aeonlib/salt/models/__init__.py | 4 ++++ src/aeonlib/salt/models/request_models.py | 24 +++++++++++++++++++++++ tests/salt/test_models.py | 23 ++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 src/aeonlib/salt/models/request_models.py diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index e69de29..2a7fe37 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -0,0 +1,4 @@ +from .request_models import Request + + +__all__ = ["Request"] diff --git a/src/aeonlib/salt/models/request_models.py b/src/aeonlib/salt/models/request_models.py new file mode 100644 index 0000000..863ee57 --- /dev/null +++ b/src/aeonlib/salt/models/request_models.py @@ -0,0 +1,24 @@ +"""This module provides Pydantic models for SALT observation requests.""" + +from typing import Annotated + +from annotated_types import MinLen +from pydantic import BaseModel + + +class Request(BaseModel): + """ + An observation request for SALT. + + Attributes + ---------- + proposal_code + Unique identifier of the proposal for which this request is submitted. + blocks + List of blocks to observe. + + """ + + proposal_code: str + + blocks: Annotated[list, MinLen(1)] diff --git a/tests/salt/test_models.py b/tests/salt/test_models.py index e69de29..fcec5ba 100644 --- a/tests/salt/test_models.py +++ b/tests/salt/test_models.py @@ -0,0 +1,23 @@ +import pytest +from pydantic import ValidationError + +from aeonlib.salt.models import Request + + +@pytest.fixture() +def base_request(): + """A simple request to edit or build from.""" + return Request(proposal_code="2025-1-SCI-042", blocks=["Dummy"]) + + +class TestRequest: + def test_request(self, base_request): + """Test that a simple request can be built.""" + assert True + + def test_no_blocks(self): + """Test that at least one block must be supplied.""" + with pytest.raises(ValidationError) as exc_info: + Request(proposal_code="2025-1-SCI-042", blocks=[]) + assert exc_info.value.errors()[0]["loc"] == ("blocks",) + assert exc_info.value.errors()[0]["type"] == "too_short" From 5773030879a7494ba2b4bf8be8710f61a7984214 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 10 Sep 2025 13:23:56 +0200 Subject: [PATCH 003/171] Add a first version of the block model --- src/aeonlib/salt/models/__init__.py | 3 +- src/aeonlib/salt/models/block_models.py | 110 ++++++++++++++++++++++++ tests/salt/test_models.py | 54 +++++++++++- 3 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 src/aeonlib/salt/models/block_models.py diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 2a7fe37..df62b0f 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,4 +1,5 @@ +from .block_models import Block from .request_models import Request -__all__ = ["Request"] +__all__ = ["Block", "Request"] diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py new file mode 100644 index 0000000..b09a548 --- /dev/null +++ b/src/aeonlib/salt/models/block_models.py @@ -0,0 +1,110 @@ +"""This module contains Pydantic models for SALT blocks.""" + +from __future__ import annotations + +from typing import Annotated, Literal, Self + +from annotated_types import Ge, Le +from pydantic import BaseModel, NonNegativeInt, PositiveInt, model_validator + +from aeonlib.models import Window + + +class Block(BaseModel): + """ + A block for SALT. + + Blocks are the smallest schedulable unit for an observation; i.e. block is either + observed in total or not ar all. Every block has a unique `identifier`, which should + only be set if you are resubmitting an existing block. + + Observing time for SALT is allocated for different priorities, and you must specify + which is the priority for your block. You also have to rank the block relative to + the other blocks in the proposal. + + The number of visits defines how often the block shall be observed in the + semester for which the submission is made. If you request more than one visit, + you can give a minimum of nights to wait between the observations. For example, + if a block is observed during the night starting on 1 September and this wait + period is 2, the next observation will only take place during the night starting + on 3 September. + + If a block spans multiple semesters, you can provide a maximum number of + observations for all semesters combined. This number must at least be equal to the + number of visits. + + Several blocks can be grouped in a pool. You can specify the pool's name if the + block shall belong to a pool. This pool must exist in the proposal already. + + Other details to specify for the block are the observation constraints, the target + to observe, the acquisition details and the instrument configuration. For time + restricted observation you also may define observing windows. + + By default, observers are notified of new data after the data reduction pipeline has + run for the night of observation. If instead you want to be notified once the data + is transferred to Cape Town (before the pipeline runs), you can set the data + notification accordingly. + + Attributes + ---------- + name + Human-friendly name for the block. This must be unique within a proposal. + identifier + Unique identifier for the block. Only set this if you are resubmitting an + existing block. + comments + Optional comments for the observer. + priority + Priority for the block. + ranking + Ranking (importance) of this block relative to the other blocks in the proposal. + num_visits + Number of visits, i.e. how often the block shall be observed in the semester + for which the submission is made. + max_num_visits + Maximum number of visits, i.e. the maximum number of times the block shall be + observed for all semesters combined. + min_nights_between_visits + Minimum number of nights to wait between subsequent observations of the block. + constraints + Observation constraints. + windows + List of time intervals during which the block shall be observed. + target + Target to observe. + acquisition + Acquisition details. + instrument + Instrument configuration. + pool + Name of the pool to which the block shall belong. The pool must exist in the + proposal already. + data_notification + When you want to be notified about new data for the block. + """ + + name: str + identifier: str | None = None + comments: str | None = None + priority: Annotated[int, Ge(0), Le(4)] + ranking: Literal["high", "medium", "low"] + num_visits: PositiveInt + max_num_visits: PositiveInt | None = None + min_nights_between_visits: NonNegativeInt = 0 + constraints: None + windows: list[Window] | None = None + target: None + acquisition: None + instrument: None + pool: str | None = None + data_notification: Literal["normal", "fast"] = "normal" + + @model_validator(mode="after") + def check_max_num_visits_is_at_least_num_visits(self) -> Self: + if self.max_num_visits: + if self.max_num_visits < self.num_visits: + raise ValueError( + "max_num_visits must be greater than or equal to num_visits." + ) + + return self diff --git a/tests/salt/test_models.py b/tests/salt/test_models.py index fcec5ba..1f067ad 100644 --- a/tests/salt/test_models.py +++ b/tests/salt/test_models.py @@ -1,13 +1,30 @@ +from contextlib import nullcontext + import pytest from pydantic import ValidationError -from aeonlib.salt.models import Request +from aeonlib.salt.models import Request, Block @pytest.fixture() -def base_request(): +def base_request(base_block): """A simple request to edit or build from.""" - return Request(proposal_code="2025-1-SCI-042", blocks=["Dummy"]) + return Request(proposal_code="2025-1-SCI-042", blocks=[base_block]) + + +@pytest.fixture() +def base_block(): + """A simple block to build or edit from.""" + return Block( + name="Test", + priority=1, + ranking="high", + num_visits=1, + constraints=None, + target=None, + acquisition=None, + instrument=None, + ) class TestRequest: @@ -21,3 +38,34 @@ def test_no_blocks(self): Request(proposal_code="2025-1-SCI-042", blocks=[]) assert exc_info.value.errors()[0]["loc"] == ("blocks",) assert exc_info.value.errors()[0]["type"] == "too_short" + + +class TestBlock: + def test_block(self, base_block): + """Test that a simple block can be built.""" + assert True + + @pytest.mark.parametrize( + "num_visits, max_num_visits, expectation", + [ + (7, None, nullcontext()), + (7, 8, nullcontext()), + (7, 7, nullcontext()), + (7, 6, pytest.raises(ValidationError, match="greater than")), + ], + ) + def test_max_visits_and_visits( + self, num_visits, max_num_visits, expectation, base_block + ): + """ + Test that the maximum number of visits must not be less than the number of + visits. + """ + block = base_block.model_dump() + block["num_visits"] = num_visits + block["max_num_visits"] = max_num_visits + + with expectation: + Block(**block) + + assert True From 905fa7f20ed0e82e0589342ed9d165783409f4d3 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 10 Sep 2025 14:22:21 +0200 Subject: [PATCH 004/171] Add a first version of the SALT sidereal target model --- src/aeonlib/salt/models/__init__.py | 3 +- src/aeonlib/salt/models/block_models.py | 3 +- src/aeonlib/salt/models/target_models.py | 270 +++++++++++++++++++++++ src/tt.txt | 238 ++++++++++++++++++++ tests/salt/test_models.py | 25 ++- 5 files changed, 534 insertions(+), 5 deletions(-) create mode 100644 src/aeonlib/salt/models/target_models.py create mode 100644 src/tt.txt diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index df62b0f..4fb1ca3 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,5 +1,6 @@ +from .target_models import SaltSiderealTarget from .block_models import Block from .request_models import Request -__all__ = ["Block", "Request"] +__all__ = ["Block", "Request", "SaltSiderealTarget"] diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index b09a548..9a5ad0a 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -8,6 +8,7 @@ from pydantic import BaseModel, NonNegativeInt, PositiveInt, model_validator from aeonlib.models import Window +from aeonlib.salt.models import SaltSiderealTarget class Block(BaseModel): @@ -93,7 +94,7 @@ class Block(BaseModel): min_nights_between_visits: NonNegativeInt = 0 constraints: None windows: list[Window] | None = None - target: None + target: SaltSiderealTarget acquisition: None instrument: None pool: str | None = None diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py new file mode 100644 index 0000000..a3a81f9 --- /dev/null +++ b/src/aeonlib/salt/models/target_models.py @@ -0,0 +1,270 @@ +"""This module contains Pydantic models for targets to observe with SALT.""" + +from __future__ import annotations + +from typing import Literal + +from aeonlib.models import SiderealTarget + + +class SaltSiderealTarget(SiderealTarget): + """ + A sidereal target to observe with SALT. + + This model extends the `SiderealTarget` model by adding a target type and a + magnitude range. + + Attributes + ---------- + target_type + Target type. This must be the label for SIMBAD object type (see + http://simbad.cds.unistra.fr/guide/otypes.htx). Examples are "TTau*" and + "StarburstG". + magnitude_range + Magnitude range for the range. + """ + + target_type: TargetType + magnitude_range: None + + +TargetType = Literal[ + "Unknown", + "Maser", + "X", + "SuperSoft", + "gamma", + "gammaBurst", + "Inexistant", + "Error", + "Gravitation", + "LensingEv", + "Candidate_Lens", + "Possible_lensImage", + "GravLens", + "GravLensSystem", + "Candidates", + "Possible_SClG", + "Possible_ClG", + "Possible_GrG", + "Candidate_**", + "Candidate_EB*", + "Candidate_CV*", + "Candidate_XB*", + "Candidate_LMXB", + "Candidate_HMXB", + "Candidate_Pec*", + "Candidate_YSO", + "Candidate_pMS*", + "Candidate_TTau*", + "Candidate_C*", + "Candidate_S*", + "Candidate_OH", + "Candidate_CH", + "Candidate_WR*", + "Candidate_Be*", + "Candidate_HB*", + "Candidate_RGB*", + "Candidate_RSG*", + "Candidate_AGB*", + "Candidate_post-AGB*", + "Candidate_BSS", + "Candidate_WD*", + "Candidate_NS", + "Candidate_BH", + "Candidate_SN*", + "Candidate_low-mass*", + "Candidate_brownD*", + "multiple_object", + "Region", + "Void", + "SuperClG", + "ClG", + "GroupG", + "Compact_Gr_G", + "Gr_QSO", + "PairG", + "IG", + "GlCl?", + "Cl*", + "GlCl", + "OpCl", + "Assoc*", + "**", + "EB*", + "EB*Algol", + "EB*betLyr", + "EB*WUMa", + "EB*Planet", + "SB", + "CataclyV*", + "DQHer", + "AMHer", + "Nova-like", + "Nova", + "DwarfNova", + "XB", + "LMXB", + "HMXB", + "***", + "ISM", + "PartofCloud", + "PN?", + "ComGlob", + "Bubble", + "EmObj", + "Cloud", + "GalNeb", + "BrNeb", + "DkNeb", + "RfNeb", + "MolCld", + "Globule", + "denseCore", + "HVCld", + "BiNeb", + "GasNeb", + "HII", + "PN", + "HIshell", + "SNR?", + "SNR", + "Circumstellar", + "outflow?", + "Outflow", + "OutflowJet", + "HH", + "Star", + "*inCl", + "*inNeb", + "*inAssoc", + "*in**", + "V*?", + "Pec*", + "HB*", + "YSO", + "Em*", + "Be*", + "BlueStraggler", + "RGB*", + "AGB*", + "C*", + "S*", + "RSG*", + "post-AGB*", + "WD*", + "pulsWD*", + "low-mass*", + "brownD*", + "OH/IR", + "CH", + "pMS*", + "TTau*", + "WR*", + "NS*", + "BH*", + "PM*", + "near*", + "HV*", + "V*", + "Irregular_V*", + "Orion_V*", + "Rapid_Irreg_V*", + "Eruptive*", + "Flare*", + "FUOr", + "Erupt*RCrB", + "RotV*", + "RotV*alf2CVn", + "RotV*Ell", + "Pulsar", + "BYDra", + "RSCVn", + "PulsV*", + "RRLyr", + "Cepheid", + "PulsV*delSct", + "PulsV*RVTau", + "PulsV*WVir", + "PulsV*bCep", + "deltaCep", + "gammaDor", + "LPV*", + "Mira", + "semi-regV*", + "SN", + "Symbiotic*", + "Sub-stellar", + "Planet?", + "ExG*", + "Galaxy", + "EllipticalG", + "SpiralG", + "DwarfG", + "IrregG", + "PartofG", + "GinCl", + "BClG", + "GinGroup", + "GinPair", + "High_z_G", + "AbsLineSystem", + "Ly-alpha_ALS", + "DLy-alpha_ALS", + "metal_ALS", + "Ly-limit_ALS", + "Broad_ALS", + "RadioG", + "HII_G", + "LSB_G", + "AGN_Candidate", + "QSO_Candidate", + "Blazar_Candidate", + "BLLac_Candidate", + "EmG", + "StarburstG", + "BlueCompG", + "LensedImage", + "LensedG", + "LensedQ", + "AGN", + "LINER", + "Seyfert", + "Seyfert_1", + "Seyfert_2", + "Blazar", + "BLLac", + "OVV", + "QSO", + "GSN", + "Solar_System", + "Planet", + "Mercury", + "Venus", + "Earth", + "Moon", + "Mars", + "Jupiter", + "Saturn", + "Uranus", + "Neptune", + "PMoon", + "PRing", + "DwarfPlanet", + "Pluto", + "Asteroid", + "Comet", + "KBO", + "Calib", + "Calib_S", + "Calib_aS", + "Calib_phS", + "Calib_sS", + "Cal_polS", + "Cal_spS", + "Cal_rvS", + "Cal_Flat", + "Cal_SFlat", + "Cal_DFlat", + "Cal_Guide*", +] diff --git a/src/tt.txt b/src/tt.txt new file mode 100644 index 0000000..41554d8 --- /dev/null +++ b/src/tt.txt @@ -0,0 +1,238 @@ +"Unknown", +"Maser", +"X", +"SuperSoft", +"gamma", +"gammaBurst", +"Inexistant", +"Error", +"Gravitation", +"LensingEv", +"Candidate_Lens", +"Possible_lensImage", +"GravLens", +"GravLensSystem", +"Candidates", +"Possible_SClG", +"Possible_ClG", +"Possible_GrG", +"Candidate_**", +"Candidate_EB*", +"Candidate_CV*", +"Candidate_XB*", +"Candidate_LMXB", +"Candidate_HMXB", +"Candidate_Pec*", +"Candidate_YSO", +"Candidate_pMS*", +"Candidate_TTau*", +"Candidate_C*", +"Candidate_S*", +"Candidate_OH", +"Candidate_CH", +"Candidate_WR*", +"Candidate_Be*", +"Candidate_HB*", +"Candidate_RGB*", +"Candidate_RSG*", +"Candidate_AGB*", +"Candidate_post-AGB*", +"Candidate_BSS", +"Candidate_WD*", +"Candidate_NS", +"Candidate_BH", +"Candidate_SN*", +"Candidate_low-mass*", +"Candidate_brownD*", +"multiple_object", +"Region", +"Void", +"SuperClG", +"ClG", +"GroupG", +"Compact_Gr_G", +"Gr_QSO", +"PairG", +"IG", +"GlCl?", +"Cl*", +"GlCl", +"OpCl", +"Assoc*", +"**", +"EB*", +"EB*Algol", +"EB*betLyr", +"EB*WUMa", +"EB*Planet", +"SB", +"CataclyV*", +"DQHer", +"AMHer", +"Nova-like", +"Nova", +"DwarfNova", +"XB", +"LMXB", +"HMXB", +"***", +"ISM", +"PartofCloud", +"PN?", +"ComGlob", +"Bubble", +"EmObj", +"Cloud", +"GalNeb", +"BrNeb", +"DkNeb", +"RfNeb", +"MolCld", +"Globule", +"denseCore", +"HVCld", +"BiNeb", +"GasNeb", +"HII", +"PN", +"HIshell", +"SNR?", +"SNR", +"Circumstellar", +"outflow?", +"Outflow", +"OutflowJet", +"HH", +"Star", +"*inCl", +"*inNeb", +"*inAssoc", +"*in**", +"V*?", +"Pec*", +"HB*", +"YSO", +"Em*", +"Be*", +"BlueStraggler", +"RGB*", +"AGB*", +"C*", +"S*", +"RSG*", +"post-AGB*", +"WD*", +"pulsWD*", +"low-mass*", +"brownD*", +"OH/IR", +"CH", +"pMS*", +"TTau*", +"WR*", +"NS*", +"BH*", +"PM*", +"near*", +"HV*", +"V*", +"Irregular_V*", +"Orion_V*", +"Rapid_Irreg_V*", +"Eruptive*", +"Flare*", +"FUOr", +"Erupt*RCrB", +"RotV*", +"RotV*alf2CVn", +"RotV*Ell", +"Pulsar", +"BYDra", +"RSCVn", +"PulsV*", +"RRLyr", +"Cepheid", +"PulsV*delSct", +"PulsV*RVTau", +"PulsV*WVir", +"PulsV*bCep", +"deltaCep", +"gammaDor", +"LPV*", +"Mira", +"semi-regV*", +"SN", +"Symbiotic*", +"Sub-stellar", +"Planet?", +"ExG*", +"Galaxy", +"EllipticalG", +"SpiralG", +"DwarfG", +"IrregG", +"PartofG", +"GinCl", +"BClG", +"GinGroup", +"GinPair", +"High_z_G", +"AbsLineSystem", +"Ly-alpha_ALS", +"DLy-alpha_ALS", +"metal_ALS", +"Ly-limit_ALS", +"Broad_ALS", +"RadioG", +"HII_G", +"LSB_G", +"AGN_Candidate", +"QSO_Candidate", +"Blazar_Candidate", +"BLLac_Candidate", +"EmG", +"StarburstG", +"BlueCompG", +"LensedImage", +"LensedG", +"LensedQ", +"AGN", +"LINER", +"Seyfert", +"Seyfert_1", +"Seyfert_2", +"Blazar", +"BLLac", +"OVV", +"QSO", +"GSN", +"Solar_System", +"Planet", +"Mercury", +"Venus", +"Earth", +"Moon", +"Mars", +"Jupiter", +"Saturn", +"Uranus", +"Neptune", +"PMoon", +"PRing", +"DwarfPlanet", +"Pluto", +"Asteroid", +"Comet", +"KBO", +"Calib", +"Calib_S", +"Calib_aS", +"Calib_phS", +"Calib_sS", +"Cal_polS", +"Cal_spS", +"Cal_rvS", +"Cal_Flat", +"Cal_SFlat", +"Cal_DFlat", +"Cal_Guide*", diff --git a/tests/salt/test_models.py b/tests/salt/test_models.py index 1f067ad..a6b2644 100644 --- a/tests/salt/test_models.py +++ b/tests/salt/test_models.py @@ -3,7 +3,7 @@ import pytest from pydantic import ValidationError -from aeonlib.salt.models import Request, Block +from aeonlib.salt.models import Request, Block, SaltSiderealTarget @pytest.fixture() @@ -13,7 +13,7 @@ def base_request(base_block): @pytest.fixture() -def base_block(): +def base_block(base_target): """A simple block to build or edit from.""" return Block( name="Test", @@ -21,12 +21,25 @@ def base_block(): ranking="high", num_visits=1, constraints=None, - target=None, + target=base_target, acquisition=None, instrument=None, ) +@pytest.fixture() +def base_target(): + """A simple sidereal target to build or edit from.""" + return SaltSiderealTarget( + name="Test Target", + type="ICRS", + ra=0, + dec=0, + target_type="Nova", + magnitude_range=None, + ) + + class TestRequest: def test_request(self, base_request): """Test that a simple request can be built.""" @@ -69,3 +82,9 @@ def test_max_visits_and_visits( Block(**block) assert True + + +class TestSaltSiderealTarget: + def test_salt_sidereal_target(self, base_target): + """Test that a simple target can be built.""" + assert True From 8b0732974e1a276b9321990af16ffadce773e9ff Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 10 Sep 2025 15:04:35 +0200 Subject: [PATCH 005/171] Add the magnitude range model --- src/aeonlib/salt/models/__init__.py | 4 +-- src/aeonlib/salt/models/target_models.py | 43 ++++++++++++++++++++++-- tests/salt/test_models.py | 38 +++++++++++++++++++-- 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 4fb1ca3..60df02e 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,6 +1,6 @@ -from .target_models import SaltSiderealTarget +from .target_models import MagnitudeRange, SaltSiderealTarget from .block_models import Block from .request_models import Request -__all__ = ["Block", "Request", "SaltSiderealTarget"] +__all__ = ["Block", "MagnitudeRange", "Request", "SaltSiderealTarget"] diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py index a3a81f9..d499f5f 100644 --- a/src/aeonlib/salt/models/target_models.py +++ b/src/aeonlib/salt/models/target_models.py @@ -2,11 +2,16 @@ from __future__ import annotations -from typing import Literal +from typing import Literal, Self + +from pydantic import BaseModel, NonNegativeFloat, model_validator from aeonlib.models import SiderealTarget +Bandpass = Literal["U", "B", "V", "R", "I"] + + class SaltSiderealTarget(SiderealTarget): """ A sidereal target to observe with SALT. @@ -25,7 +30,41 @@ class SaltSiderealTarget(SiderealTarget): """ target_type: TargetType - magnitude_range: None + magnitude_range: MagnitudeRange + + +class MagnitudeRange(BaseModel): + """ + A magnitude range. + + The minimum (brightest) and maximum (faintest) magnitude must be give for a + particular bandpass filter. + + Attributes + ---------- + min_magnitude + Minimum (brightest) magnitude. + max_magnitude + Maximum (faintest) magnitude. This must be greater than or equal to the minimum + magnitude. + bandpass + Bandpass filter for which the magnitude range is given. + """ + + min_magnitude: NonNegativeFloat + + max_magnitude: NonNegativeFloat + + bandpass: Bandpass + + @model_validator(mode="after") + def check_max_magnitude_is_at_least_min_magnitude(self) -> Self: + if self.min_magnitude > self.max_magnitude: + raise ValueError( + "max_magnitude must be greater than or equal to min_magnitude." + ) + + return self TargetType = Literal[ diff --git a/tests/salt/test_models.py b/tests/salt/test_models.py index a6b2644..16e7e8a 100644 --- a/tests/salt/test_models.py +++ b/tests/salt/test_models.py @@ -3,7 +3,7 @@ import pytest from pydantic import ValidationError -from aeonlib.salt.models import Request, Block, SaltSiderealTarget +from aeonlib.salt.models import Request, Block, MagnitudeRange, SaltSiderealTarget @pytest.fixture() @@ -28,7 +28,7 @@ def base_block(base_target): @pytest.fixture() -def base_target(): +def base_target(base_magnitude_range): """A simple sidereal target to build or edit from.""" return SaltSiderealTarget( name="Test Target", @@ -36,10 +36,16 @@ def base_target(): ra=0, dec=0, target_type="Nova", - magnitude_range=None, + magnitude_range=base_magnitude_range, ) +@pytest.fixture() +def base_magnitude_range(): + """A simple magnitude range to build or edit from.""" + return MagnitudeRange(min_magnitude=17.1, max_magnitude=17.5, bandpass="V") + + class TestRequest: def test_request(self, base_request): """Test that a simple request can be built.""" @@ -88,3 +94,29 @@ class TestSaltSiderealTarget: def test_salt_sidereal_target(self, base_target): """Test that a simple target can be built.""" assert True + + +class TestMagnitudeRange: + def test_magnitude_range(self, base_magnitude_range): + """Test that a simple magnitude range can be built.""" + assert True + + @pytest.mark.parametrize( + "min_magnitude, max_magnitude, expectation", + [ + (17.1, 17.4, nullcontext()), + (17.1, 17.1, nullcontext()), + (17.1, 17.09, pytest.raises(ValidationError, match="greater than")), + ], + ) + def test_min_and_max_magnitude( + self, min_magnitude, max_magnitude, expectation, base_magnitude_range + ): + magnitude_range = base_magnitude_range.model_dump() + magnitude_range["min_magnitude"] = min_magnitude + magnitude_range["max_magnitude"] = max_magnitude + + with expectation: + MagnitudeRange(**magnitude_range) + + assert True From be49a166d6ba79fb936fd328046e9d29396d6164 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 11 Sep 2025 13:45:05 +0200 Subject: [PATCH 006/171] Add Pydantic validators for checking greater than or equal to and less than or equal to relations --- src/aeonlib/salt/validators.py | 75 ++++++++++++++++++++++++++++++++++ tests/salt/test_validators.py | 33 +++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/aeonlib/salt/validators.py create mode 100644 tests/salt/test_validators.py diff --git a/src/aeonlib/salt/validators.py b/src/aeonlib/salt/validators.py new file mode 100644 index 0000000..686eb0f --- /dev/null +++ b/src/aeonlib/salt/validators.py @@ -0,0 +1,75 @@ +"""This module defines some Pydantic validators.""" + +from typing import Any + +from pydantic import AfterValidator + + +def _check_ge(a: Any, b: Any) -> None: + if a < b: + raise ValueError(f"{a} is not greater than or equal to {b}.") + + +def _check_le(a: Any, b: Any) -> None: + if a > b: + raise ValueError(f"{a} is not less than or equal to {b}.") + + +def GreaterEqual(value: Any): + """ + Return a Pydantic validator for checking a greater or equal than relation. + + The returned validator can be used in a type annotation:: + + import pydantic + + class DummyModel(pydantic.BaseModel): + duration: Annotated[float, GreaterEqual(4)] + + Pydantic will then first perform its own internal validation and then check + whether the field value is greater than or equal to the argument passed to + `GreaterEqual` (4 in the example above). + + It is up to the user to ensure that the field value and the argument of + `GreaterEqual` can be compared. + + Parameters + ---------- + value + Value against which to compare. + + Returns + ------- + A validator for checking a greater than or equal to relation. + """ + return AfterValidator(lambda v: _check_ge(v, value)) + + +def LessEqual(value: Any): + """ + Return a Pydantic validator for checking a less or equal than relation. + + The returned validator can be used in a type annotation:: + + import pydantic + + class DummyModel(pydantic.BaseModel): + height: Annotated[float, LessEqual(4)] + + Pydantic will then first perform its own internal validation and then check + whether the field value is less than or equal to the argument passed to + `LessEqual` (4 in the example above). + + It is up to the user to ensure that the field value and the argument of + `LessEqual` can be compared. + + Parameters + ---------- + value + Value against which to compare. + + Returns + ------- + A validator for checking a less than or equal to relation. + """ + return AfterValidator(lambda v: _check_le(v, value)) diff --git a/tests/salt/test_validators.py b/tests/salt/test_validators.py new file mode 100644 index 0000000..acbaab9 --- /dev/null +++ b/tests/salt/test_validators.py @@ -0,0 +1,33 @@ +from contextlib import nullcontext +from typing import Annotated + +import pytest +from pydantic import BaseModel, ValidationError + +from aeonlib.salt.validators import GreaterEqual, LessEqual + + +class GreaterEqualModel(BaseModel): + a: Annotated[int, GreaterEqual(4)] + + +class LessEqualModel(BaseModel): + a: Annotated[int, LessEqual(4)] + + +class TestValidators: + @pytest.mark.parametrize( + "a, expectation", + [(3, pytest.raises(ValidationError)), (4, nullcontext()), (5, nullcontext())], + ) + def test_greater_equal(self, a, expectation): + with expectation: + GreaterEqualModel(a=a) + + @pytest.mark.parametrize( + "a, expectation", + [(3, nullcontext()), (4, nullcontext()), (5, pytest.raises(ValidationError))], + ) + def test_less_equal(self, a, expectation): + with expectation: + LessEqualModel(a=a) From 0dc058b21a0ff0047bcce448db70665805ebdcda Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 11 Sep 2025 14:37:50 +0200 Subject: [PATCH 007/171] Add the constraints model --- src/aeonlib/salt/models/__init__.py | 4 +- src/aeonlib/salt/models/block_models.py | 56 +++++++++++++++++++++++-- tests/salt/test_models.py | 28 ++++++++++++- 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 60df02e..94a832c 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,6 +1,6 @@ from .target_models import MagnitudeRange, SaltSiderealTarget -from .block_models import Block +from .block_models import Block, Constraints from .request_models import Request -__all__ = ["Block", "MagnitudeRange", "Request", "SaltSiderealTarget"] +__all__ = ["Block", "Constraints", "MagnitudeRange", "Request", "SaltSiderealTarget"] diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 9a5ad0a..5c7d612 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -4,11 +4,22 @@ from typing import Annotated, Literal, Self +import astropy.units as u from annotated_types import Ge, Le -from pydantic import BaseModel, NonNegativeInt, PositiveInt, model_validator - -from aeonlib.models import Window +from pydantic import ( + BaseModel, + NonNegativeInt, + NonNegativeFloat, + PositiveInt, + PositiveFloat, + model_validator, +) + +from aeonlib.models import Angle, Window from aeonlib.salt.models import SaltSiderealTarget +from aeonlib.salt.validators import GreaterEqual, LessEqual + +Transparency = Literal["clear", "thin cloud", "thick cloud", "any"] class Block(BaseModel): @@ -109,3 +120,42 @@ def check_max_num_visits_is_at_least_num_visits(self) -> Self: ) return self + + +class Constraints(BaseModel): + """ + Observing constraints. + + An observation can be constrained by the sky transparency, the Moon phase, the lunar + distance and the seeing. + + The lunar phase is specified in terms of what percentage p of the lunar disk is + illuminated. For New Moon p is 0, for Full Moon it is 100. In general, p and the + lunar elongation e (i.e., the angle between Sun, observer on Earth and Moon) are + related by p = 100% * (1 - cos(e)) / 2. + + The lunar distance is the angle between the target to observe, Earth and Moon. + + The lunar phase and distance are only relevant if the Moon is above the horizon. + + The seeing must be given for the zenith. + + Attributes + ---------- + transparency + Required sky transparency. + max_lunar_phase_percentage + Maximum allowed Lunar phase, as a percentage. This is the percentage of the + lunar disk which is illuminated. + min_lunar_distance + Minimum required lunar distance. + max_seeing + Maximum allowed seeing. + """ + + transparency: Transparency + max_lunar_phase_percentage: Annotated[NonNegativeFloat, LessEqual(100)] + min_lunar_distance: Annotated[ + Angle, GreaterEqual(0 * u.deg), LessEqual(180 * u.deg) + ] + max_seeing: PositiveFloat diff --git a/tests/salt/test_models.py b/tests/salt/test_models.py index 16e7e8a..d84570e 100644 --- a/tests/salt/test_models.py +++ b/tests/salt/test_models.py @@ -1,9 +1,16 @@ from contextlib import nullcontext +import astropy.units as u import pytest from pydantic import ValidationError -from aeonlib.salt.models import Request, Block, MagnitudeRange, SaltSiderealTarget +from aeonlib.salt.models import ( + Request, + Block, + MagnitudeRange, + SaltSiderealTarget, + Constraints, +) @pytest.fixture() @@ -46,6 +53,16 @@ def base_magnitude_range(): return MagnitudeRange(min_magnitude=17.1, max_magnitude=17.5, bandpass="V") +@pytest.fixture() +def base_constraints(): + return Constraints( + transparency="thick cloud", + max_lunar_phase_percentage=50, + min_lunar_distance=45 * u.deg, + max_seeing=3, + ) + + class TestRequest: def test_request(self, base_request): """Test that a simple request can be built.""" @@ -112,6 +129,9 @@ def test_magnitude_range(self, base_magnitude_range): def test_min_and_max_magnitude( self, min_magnitude, max_magnitude, expectation, base_magnitude_range ): + """ + Test that the maximum magnitude must not be less than the minimum magnitude. + """ magnitude_range = base_magnitude_range.model_dump() magnitude_range["min_magnitude"] = min_magnitude magnitude_range["max_magnitude"] = max_magnitude @@ -120,3 +140,9 @@ def test_min_and_max_magnitude( MagnitudeRange(**magnitude_range) assert True + + +class TestConstraints: + def test_constraints(self, base_constraints): + """Test that constraints can be built.""" + assert True From 8860589b540526c4c525f6a37c819228bc6d62a1 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 11 Sep 2025 14:39:49 +0200 Subject: [PATCH 008/171] Replace the Ge and Le validators with GreaterEqual and LessEqual --- src/aeonlib/salt/models/block_models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 5c7d612..8ee71be 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -5,7 +5,6 @@ from typing import Annotated, Literal, Self import astropy.units as u -from annotated_types import Ge, Le from pydantic import ( BaseModel, NonNegativeInt, @@ -98,7 +97,7 @@ class Block(BaseModel): name: str identifier: str | None = None comments: str | None = None - priority: Annotated[int, Ge(0), Le(4)] + priority: Annotated[int, GreaterEqual(0), LessEqual(4)] ranking: Literal["high", "medium", "low"] num_visits: PositiveInt max_num_visits: PositiveInt | None = None From b72c1e7af48d137b97f75bf49c1862f4b3dbb788 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 11 Sep 2025 16:47:04 +0200 Subject: [PATCH 009/171] Handle the case of zero maximum visits correctly --- src/aeonlib/salt/models/block_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 8ee71be..b52cea6 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -112,7 +112,7 @@ class Block(BaseModel): @model_validator(mode="after") def check_max_num_visits_is_at_least_num_visits(self) -> Self: - if self.max_num_visits: + if self.max_num_visits is not None: if self.max_num_visits < self.num_visits: raise ValueError( "max_num_visits must be greater than or equal to num_visits." From c42efbc1c351259667561fb11c1e8f507d04f576 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 11 Sep 2025 16:49:54 +0200 Subject: [PATCH 010/171] Use an AstroPy Angle instead of an AstroPy Quantity --- tests/salt/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/salt/test_models.py b/tests/salt/test_models.py index d84570e..afa38a8 100644 --- a/tests/salt/test_models.py +++ b/tests/salt/test_models.py @@ -58,7 +58,7 @@ def base_constraints(): return Constraints( transparency="thick cloud", max_lunar_phase_percentage=50, - min_lunar_distance=45 * u.deg, + min_lunar_distance=astropy.coordinates.Angle("45d"), max_seeing=3, ) From 17b4a3fd92e4249d8a7dc0722d3cab69f5329dc9 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 11 Sep 2025 16:50:19 +0200 Subject: [PATCH 011/171] Fix the GreaterEqual and LessEqual validator --- src/aeonlib/salt/validators.py | 4 +++- tests/salt/test_validators.py | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/validators.py b/src/aeonlib/salt/validators.py index 686eb0f..8329334 100644 --- a/src/aeonlib/salt/validators.py +++ b/src/aeonlib/salt/validators.py @@ -5,14 +5,16 @@ from pydantic import AfterValidator -def _check_ge(a: Any, b: Any) -> None: +def _check_ge(a: Any, b: Any) -> Any: if a < b: raise ValueError(f"{a} is not greater than or equal to {b}.") + return a def _check_le(a: Any, b: Any) -> None: if a > b: raise ValueError(f"{a} is not less than or equal to {b}.") + return a def GreaterEqual(value: Any): diff --git a/tests/salt/test_validators.py b/tests/salt/test_validators.py index acbaab9..f71c74b 100644 --- a/tests/salt/test_validators.py +++ b/tests/salt/test_validators.py @@ -24,6 +24,10 @@ def test_greater_equal(self, a, expectation): with expectation: GreaterEqualModel(a=a) + def test_greater_equal_does_not_change_field_value(self): + """Test that the field value is not changed by the GreaterEqual validator.""" + assert GreaterEqualModel(a=7).a == 7 + @pytest.mark.parametrize( "a, expectation", [(3, nullcontext()), (4, nullcontext()), (5, pytest.raises(ValidationError))], @@ -31,3 +35,7 @@ def test_greater_equal(self, a, expectation): def test_less_equal(self, a, expectation): with expectation: LessEqualModel(a=a) + + def test_less_equal_does_not_change_field_value(self): + """Test that the field value is not changed by the LessEqual validator.""" + assert LessEqualModel(a=2).a == 2 From 91fe20a8f89ffe63906e3ad9c42ab65afdeec769 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 11 Sep 2025 16:52:25 +0200 Subject: [PATCH 012/171] Allow only right ascension values supported by SALT --- src/aeonlib/salt/models/target_models.py | 8 ++++++++ tests/salt/test_models.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py index d499f5f..3c3db84 100644 --- a/src/aeonlib/salt/models/target_models.py +++ b/src/aeonlib/salt/models/target_models.py @@ -4,6 +4,7 @@ from typing import Literal, Self +import astropy.units as u from pydantic import BaseModel, NonNegativeFloat, model_validator from aeonlib.models import SiderealTarget @@ -32,6 +33,13 @@ class SaltSiderealTarget(SiderealTarget): target_type: TargetType magnitude_range: MagnitudeRange + @model_validator(mode="after") + def check_target_viewable(self): + if self.ra < -76 * u.deg or self.ra > 11 * u.deg: + raise ValueError("ra not between -76 and 11 degrees.") + + return self + class MagnitudeRange(BaseModel): """ diff --git a/tests/salt/test_models.py b/tests/salt/test_models.py index afa38a8..5e84cab 100644 --- a/tests/salt/test_models.py +++ b/tests/salt/test_models.py @@ -1,5 +1,6 @@ from contextlib import nullcontext +import astropy.coordinates import astropy.units as u import pytest from pydantic import ValidationError @@ -112,6 +113,22 @@ def test_salt_sidereal_target(self, base_target): """Test that a simple target can be built.""" assert True + @pytest.mark.parametrize( + "ra, expectation", + [ + (astropy.coordinates.Angle("-76.001d"), pytest.raises(ValueError)), + (astropy.coordinates.Angle("-76d"), nullcontext()), + (astropy.coordinates.Angle("11d"), nullcontext()), + (astropy.coordinates.Angle("11.0001d"), pytest.raises(ValueError)), + ], + ) + def test_ra_range(self, ra, expectation, base_target): + """Test that the right ascension must be between -76 and 11 degrees.""" + target = base_target.model_dump() + target["ra"] = ra + with expectation: + SaltSiderealTarget(**target) + class TestMagnitudeRange: def test_magnitude_range(self, base_magnitude_range): From 0639e019adfdbbad37b6dfdb5389b9ff9336f4a8 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 11 Sep 2025 17:28:00 +0200 Subject: [PATCH 013/171] Move model tests into separate files --- tests/salt/models/conftest.py | 60 +++++++++ tests/salt/models/test_block_models.py | 43 ++++++ tests/salt/models/test_request_models.py | 17 +++ tests/salt/models/test_target_models.py | 58 ++++++++ tests/salt/test_models.py | 165 ----------------------- 5 files changed, 178 insertions(+), 165 deletions(-) create mode 100644 tests/salt/models/conftest.py create mode 100644 tests/salt/models/test_block_models.py create mode 100644 tests/salt/models/test_request_models.py create mode 100644 tests/salt/models/test_target_models.py delete mode 100644 tests/salt/test_models.py diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py new file mode 100644 index 0000000..f317d86 --- /dev/null +++ b/tests/salt/models/conftest.py @@ -0,0 +1,60 @@ +import astropy.coordinates +import pytest + +from aeonlib.salt.models import ( + Request, + Block, + MagnitudeRange, + SaltSiderealTarget, + Constraints, +) + + +@pytest.fixture() +def base_request(base_block): + """A simple request to edit or build from.""" + return Request(proposal_code="2025-1-SCI-042", blocks=[base_block]) + + +@pytest.fixture() +def base_block(base_target): + """A simple block to build or edit from.""" + return Block( + name="Test", + priority=1, + ranking="high", + num_visits=1, + constraints=None, + target=base_target, + acquisition=None, + instrument=None, + ) + + +@pytest.fixture() +def base_target(base_magnitude_range): + """A simple sidereal target to build or edit from.""" + return SaltSiderealTarget( + name="Test Target", + type="ICRS", + ra=0, + dec=0, + target_type="Nova", + magnitude_range=base_magnitude_range, + ) + + +@pytest.fixture() +def base_magnitude_range(): + """A simple magnitude range to build or edit from.""" + return MagnitudeRange(min_magnitude=17.1, max_magnitude=17.5, bandpass="V") + + +@pytest.fixture() +def base_constraints(): + return Constraints( + transparency="thick cloud", + max_lunar_phase_percentage=50, + min_lunar_distance=astropy.coordinates.Angle("45d"), + max_seeing=3, + ) diff --git a/tests/salt/models/test_block_models.py b/tests/salt/models/test_block_models.py new file mode 100644 index 0000000..8db553e --- /dev/null +++ b/tests/salt/models/test_block_models.py @@ -0,0 +1,43 @@ +from contextlib import nullcontext + +import pytest +from pydantic import ValidationError + +from aeonlib.salt.models import Block + + +class TestBlock: + def test_block(self, base_block): + """Test that a simple block can be built.""" + assert True + + @pytest.mark.parametrize( + "num_visits, max_num_visits, expectation", + [ + (7, None, nullcontext()), + (7, 8, nullcontext()), + (7, 7, nullcontext()), + (7, 6, pytest.raises(ValidationError, match="greater than")), + ], + ) + def test_max_visits_and_visits( + self, num_visits, max_num_visits, expectation, base_block + ): + """ + Test that the maximum number of visits must not be less than the number of + visits. + """ + block = base_block.model_dump() + block["num_visits"] = num_visits + block["max_num_visits"] = max_num_visits + + with expectation: + Block(**block) + + assert True + + +class TestConstraints: + def test_constraints(self, base_constraints): + """Test that constraints can be built.""" + assert True diff --git a/tests/salt/models/test_request_models.py b/tests/salt/models/test_request_models.py new file mode 100644 index 0000000..05090a4 --- /dev/null +++ b/tests/salt/models/test_request_models.py @@ -0,0 +1,17 @@ +import pytest +from pydantic import ValidationError + +from aeonlib.salt.models import Request + + +class TestRequest: + def test_request(self, base_request): + """Test that a simple request can be built.""" + assert True + + def test_no_blocks(self): + """Test that at least one block must be supplied.""" + with pytest.raises(ValidationError) as exc_info: + Request(proposal_code="2025-1-SCI-042", blocks=[]) + assert exc_info.value.errors()[0]["loc"] == ("blocks",) + assert exc_info.value.errors()[0]["type"] == "too_short" diff --git a/tests/salt/models/test_target_models.py b/tests/salt/models/test_target_models.py new file mode 100644 index 0000000..59e1788 --- /dev/null +++ b/tests/salt/models/test_target_models.py @@ -0,0 +1,58 @@ +from contextlib import nullcontext + +import astropy.coordinates +import pytest +from pydantic import ValidationError + +from aeonlib.salt.models import MagnitudeRange, SaltSiderealTarget + + +class TestSaltSiderealTarget: + def test_salt_sidereal_target(self, base_target): + """Test that a simple target can be built.""" + assert True + + @pytest.mark.parametrize( + "ra, expectation", + [ + (astropy.coordinates.Angle("-76.001d"), pytest.raises(ValueError)), + (astropy.coordinates.Angle("-76d"), nullcontext()), + (astropy.coordinates.Angle("11d"), nullcontext()), + (astropy.coordinates.Angle("11.0001d"), pytest.raises(ValueError)), + ], + ) + def test_ra_range(self, ra, expectation, base_target): + """Test that the right ascension must be between -76 and 11 degrees.""" + target = base_target.model_dump() + target["ra"] = ra + with expectation: + SaltSiderealTarget(**target) + + +class TestMagnitudeRange: + def test_magnitude_range(self, base_magnitude_range): + """Test that a simple magnitude range can be built.""" + assert True + + @pytest.mark.parametrize( + "min_magnitude, max_magnitude, expectation", + [ + (17.1, 17.4, nullcontext()), + (17.1, 17.1, nullcontext()), + (17.1, 17.09, pytest.raises(ValidationError, match="greater than")), + ], + ) + def test_min_and_max_magnitude( + self, min_magnitude, max_magnitude, expectation, base_magnitude_range + ): + """ + Test that the maximum magnitude must not be less than the minimum magnitude. + """ + magnitude_range = base_magnitude_range.model_dump() + magnitude_range["min_magnitude"] = min_magnitude + magnitude_range["max_magnitude"] = max_magnitude + + with expectation: + MagnitudeRange(**magnitude_range) + + assert True diff --git a/tests/salt/test_models.py b/tests/salt/test_models.py deleted file mode 100644 index 5e84cab..0000000 --- a/tests/salt/test_models.py +++ /dev/null @@ -1,165 +0,0 @@ -from contextlib import nullcontext - -import astropy.coordinates -import astropy.units as u -import pytest -from pydantic import ValidationError - -from aeonlib.salt.models import ( - Request, - Block, - MagnitudeRange, - SaltSiderealTarget, - Constraints, -) - - -@pytest.fixture() -def base_request(base_block): - """A simple request to edit or build from.""" - return Request(proposal_code="2025-1-SCI-042", blocks=[base_block]) - - -@pytest.fixture() -def base_block(base_target): - """A simple block to build or edit from.""" - return Block( - name="Test", - priority=1, - ranking="high", - num_visits=1, - constraints=None, - target=base_target, - acquisition=None, - instrument=None, - ) - - -@pytest.fixture() -def base_target(base_magnitude_range): - """A simple sidereal target to build or edit from.""" - return SaltSiderealTarget( - name="Test Target", - type="ICRS", - ra=0, - dec=0, - target_type="Nova", - magnitude_range=base_magnitude_range, - ) - - -@pytest.fixture() -def base_magnitude_range(): - """A simple magnitude range to build or edit from.""" - return MagnitudeRange(min_magnitude=17.1, max_magnitude=17.5, bandpass="V") - - -@pytest.fixture() -def base_constraints(): - return Constraints( - transparency="thick cloud", - max_lunar_phase_percentage=50, - min_lunar_distance=astropy.coordinates.Angle("45d"), - max_seeing=3, - ) - - -class TestRequest: - def test_request(self, base_request): - """Test that a simple request can be built.""" - assert True - - def test_no_blocks(self): - """Test that at least one block must be supplied.""" - with pytest.raises(ValidationError) as exc_info: - Request(proposal_code="2025-1-SCI-042", blocks=[]) - assert exc_info.value.errors()[0]["loc"] == ("blocks",) - assert exc_info.value.errors()[0]["type"] == "too_short" - - -class TestBlock: - def test_block(self, base_block): - """Test that a simple block can be built.""" - assert True - - @pytest.mark.parametrize( - "num_visits, max_num_visits, expectation", - [ - (7, None, nullcontext()), - (7, 8, nullcontext()), - (7, 7, nullcontext()), - (7, 6, pytest.raises(ValidationError, match="greater than")), - ], - ) - def test_max_visits_and_visits( - self, num_visits, max_num_visits, expectation, base_block - ): - """ - Test that the maximum number of visits must not be less than the number of - visits. - """ - block = base_block.model_dump() - block["num_visits"] = num_visits - block["max_num_visits"] = max_num_visits - - with expectation: - Block(**block) - - assert True - - -class TestSaltSiderealTarget: - def test_salt_sidereal_target(self, base_target): - """Test that a simple target can be built.""" - assert True - - @pytest.mark.parametrize( - "ra, expectation", - [ - (astropy.coordinates.Angle("-76.001d"), pytest.raises(ValueError)), - (astropy.coordinates.Angle("-76d"), nullcontext()), - (astropy.coordinates.Angle("11d"), nullcontext()), - (astropy.coordinates.Angle("11.0001d"), pytest.raises(ValueError)), - ], - ) - def test_ra_range(self, ra, expectation, base_target): - """Test that the right ascension must be between -76 and 11 degrees.""" - target = base_target.model_dump() - target["ra"] = ra - with expectation: - SaltSiderealTarget(**target) - - -class TestMagnitudeRange: - def test_magnitude_range(self, base_magnitude_range): - """Test that a simple magnitude range can be built.""" - assert True - - @pytest.mark.parametrize( - "min_magnitude, max_magnitude, expectation", - [ - (17.1, 17.4, nullcontext()), - (17.1, 17.1, nullcontext()), - (17.1, 17.09, pytest.raises(ValidationError, match="greater than")), - ], - ) - def test_min_and_max_magnitude( - self, min_magnitude, max_magnitude, expectation, base_magnitude_range - ): - """ - Test that the maximum magnitude must not be less than the minimum magnitude. - """ - magnitude_range = base_magnitude_range.model_dump() - magnitude_range["min_magnitude"] = min_magnitude - magnitude_range["max_magnitude"] = max_magnitude - - with expectation: - MagnitudeRange(**magnitude_range) - - assert True - - -class TestConstraints: - def test_constraints(self, base_constraints): - """Test that constraints can be built.""" - assert True From 588a1e54d737a2ce128e436b2469b044cf0b36c0 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 11 Sep 2025 18:23:53 +0200 Subject: [PATCH 014/171] Move literal types into separate files --- src/aeonlib/salt/models/block_models.py | 5 +- src/aeonlib/salt/models/target_models.py | 250 +--------------------- src/aeonlib/salt/models/types/__init__.py | 4 + src/aeonlib/salt/models/types/block.py | 6 + src/aeonlib/salt/models/types/target.py | 249 +++++++++++++++++++++ 5 files changed, 264 insertions(+), 250 deletions(-) create mode 100644 src/aeonlib/salt/models/types/__init__.py create mode 100644 src/aeonlib/salt/models/types/block.py create mode 100644 src/aeonlib/salt/models/types/target.py diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index b52cea6..21c8e15 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -16,10 +16,9 @@ from aeonlib.models import Angle, Window from aeonlib.salt.models import SaltSiderealTarget +from aeonlib.salt.models.types import SkyTransparency from aeonlib.salt.validators import GreaterEqual, LessEqual -Transparency = Literal["clear", "thin cloud", "thick cloud", "any"] - class Block(BaseModel): """ @@ -152,7 +151,7 @@ class Constraints(BaseModel): Maximum allowed seeing. """ - transparency: Transparency + transparency: SkyTransparency max_lunar_phase_percentage: Annotated[NonNegativeFloat, LessEqual(100)] min_lunar_distance: Annotated[ Angle, GreaterEqual(0 * u.deg), LessEqual(180 * u.deg) diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py index 3c3db84..5eb6063 100644 --- a/src/aeonlib/salt/models/target_models.py +++ b/src/aeonlib/salt/models/target_models.py @@ -2,15 +2,13 @@ from __future__ import annotations -from typing import Literal, Self +from typing import Self import astropy.units as u from pydantic import BaseModel, NonNegativeFloat, model_validator from aeonlib.models import SiderealTarget - - -Bandpass = Literal["U", "B", "V", "R", "I"] +from aeonlib.salt.models.types import MagnitudeBandpass, TargetType class SaltSiderealTarget(SiderealTarget): @@ -63,7 +61,7 @@ class MagnitudeRange(BaseModel): max_magnitude: NonNegativeFloat - bandpass: Bandpass + bandpass: MagnitudeBandpass @model_validator(mode="after") def check_max_magnitude_is_at_least_min_magnitude(self) -> Self: @@ -73,245 +71,3 @@ def check_max_magnitude_is_at_least_min_magnitude(self) -> Self: ) return self - - -TargetType = Literal[ - "Unknown", - "Maser", - "X", - "SuperSoft", - "gamma", - "gammaBurst", - "Inexistant", - "Error", - "Gravitation", - "LensingEv", - "Candidate_Lens", - "Possible_lensImage", - "GravLens", - "GravLensSystem", - "Candidates", - "Possible_SClG", - "Possible_ClG", - "Possible_GrG", - "Candidate_**", - "Candidate_EB*", - "Candidate_CV*", - "Candidate_XB*", - "Candidate_LMXB", - "Candidate_HMXB", - "Candidate_Pec*", - "Candidate_YSO", - "Candidate_pMS*", - "Candidate_TTau*", - "Candidate_C*", - "Candidate_S*", - "Candidate_OH", - "Candidate_CH", - "Candidate_WR*", - "Candidate_Be*", - "Candidate_HB*", - "Candidate_RGB*", - "Candidate_RSG*", - "Candidate_AGB*", - "Candidate_post-AGB*", - "Candidate_BSS", - "Candidate_WD*", - "Candidate_NS", - "Candidate_BH", - "Candidate_SN*", - "Candidate_low-mass*", - "Candidate_brownD*", - "multiple_object", - "Region", - "Void", - "SuperClG", - "ClG", - "GroupG", - "Compact_Gr_G", - "Gr_QSO", - "PairG", - "IG", - "GlCl?", - "Cl*", - "GlCl", - "OpCl", - "Assoc*", - "**", - "EB*", - "EB*Algol", - "EB*betLyr", - "EB*WUMa", - "EB*Planet", - "SB", - "CataclyV*", - "DQHer", - "AMHer", - "Nova-like", - "Nova", - "DwarfNova", - "XB", - "LMXB", - "HMXB", - "***", - "ISM", - "PartofCloud", - "PN?", - "ComGlob", - "Bubble", - "EmObj", - "Cloud", - "GalNeb", - "BrNeb", - "DkNeb", - "RfNeb", - "MolCld", - "Globule", - "denseCore", - "HVCld", - "BiNeb", - "GasNeb", - "HII", - "PN", - "HIshell", - "SNR?", - "SNR", - "Circumstellar", - "outflow?", - "Outflow", - "OutflowJet", - "HH", - "Star", - "*inCl", - "*inNeb", - "*inAssoc", - "*in**", - "V*?", - "Pec*", - "HB*", - "YSO", - "Em*", - "Be*", - "BlueStraggler", - "RGB*", - "AGB*", - "C*", - "S*", - "RSG*", - "post-AGB*", - "WD*", - "pulsWD*", - "low-mass*", - "brownD*", - "OH/IR", - "CH", - "pMS*", - "TTau*", - "WR*", - "NS*", - "BH*", - "PM*", - "near*", - "HV*", - "V*", - "Irregular_V*", - "Orion_V*", - "Rapid_Irreg_V*", - "Eruptive*", - "Flare*", - "FUOr", - "Erupt*RCrB", - "RotV*", - "RotV*alf2CVn", - "RotV*Ell", - "Pulsar", - "BYDra", - "RSCVn", - "PulsV*", - "RRLyr", - "Cepheid", - "PulsV*delSct", - "PulsV*RVTau", - "PulsV*WVir", - "PulsV*bCep", - "deltaCep", - "gammaDor", - "LPV*", - "Mira", - "semi-regV*", - "SN", - "Symbiotic*", - "Sub-stellar", - "Planet?", - "ExG*", - "Galaxy", - "EllipticalG", - "SpiralG", - "DwarfG", - "IrregG", - "PartofG", - "GinCl", - "BClG", - "GinGroup", - "GinPair", - "High_z_G", - "AbsLineSystem", - "Ly-alpha_ALS", - "DLy-alpha_ALS", - "metal_ALS", - "Ly-limit_ALS", - "Broad_ALS", - "RadioG", - "HII_G", - "LSB_G", - "AGN_Candidate", - "QSO_Candidate", - "Blazar_Candidate", - "BLLac_Candidate", - "EmG", - "StarburstG", - "BlueCompG", - "LensedImage", - "LensedG", - "LensedQ", - "AGN", - "LINER", - "Seyfert", - "Seyfert_1", - "Seyfert_2", - "Blazar", - "BLLac", - "OVV", - "QSO", - "GSN", - "Solar_System", - "Planet", - "Mercury", - "Venus", - "Earth", - "Moon", - "Mars", - "Jupiter", - "Saturn", - "Uranus", - "Neptune", - "PMoon", - "PRing", - "DwarfPlanet", - "Pluto", - "Asteroid", - "Comet", - "KBO", - "Calib", - "Calib_S", - "Calib_aS", - "Calib_phS", - "Calib_sS", - "Cal_polS", - "Cal_spS", - "Cal_rvS", - "Cal_Flat", - "Cal_SFlat", - "Cal_DFlat", - "Cal_Guide*", -] diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py new file mode 100644 index 0000000..0b5245a --- /dev/null +++ b/src/aeonlib/salt/models/types/__init__.py @@ -0,0 +1,4 @@ +from .block import SkyTransparency +from .target import MagnitudeBandpass, TargetType + +__all__ = ["MagnitudeBandpass", "SkyTransparency", "TargetType"] diff --git a/src/aeonlib/salt/models/types/block.py b/src/aeonlib/salt/models/types/block.py new file mode 100644 index 0000000..6c7993b --- /dev/null +++ b/src/aeonlib/salt/models/types/block.py @@ -0,0 +1,6 @@ +"""This module contains types for type-hinting blocks.""" + +from typing import Literal + +SkyTransparency = Literal["clear", "thin cloud", "thick cloud", "any"] +"""Sky transparency.""" diff --git a/src/aeonlib/salt/models/types/target.py b/src/aeonlib/salt/models/types/target.py new file mode 100644 index 0000000..cdd7c40 --- /dev/null +++ b/src/aeonlib/salt/models/types/target.py @@ -0,0 +1,249 @@ +"""This module contains types for type-hinting targets.""" + +from typing import Literal + +MagnitudeBandpass = Literal["U", "B", "V", "R", "I"] +"""The bandpass filters whivh msay be used for specifying a target magnitude.""" + + +TargetType = Literal[ + "Unknown", + "Maser", + "X", + "SuperSoft", + "gamma", + "gammaBurst", + "Inexistant", + "Error", + "Gravitation", + "LensingEv", + "Candidate_Lens", + "Possible_lensImage", + "GravLens", + "GravLensSystem", + "Candidates", + "Possible_SClG", + "Possible_ClG", + "Possible_GrG", + "Candidate_**", + "Candidate_EB*", + "Candidate_CV*", + "Candidate_XB*", + "Candidate_LMXB", + "Candidate_HMXB", + "Candidate_Pec*", + "Candidate_YSO", + "Candidate_pMS*", + "Candidate_TTau*", + "Candidate_C*", + "Candidate_S*", + "Candidate_OH", + "Candidate_CH", + "Candidate_WR*", + "Candidate_Be*", + "Candidate_HB*", + "Candidate_RGB*", + "Candidate_RSG*", + "Candidate_AGB*", + "Candidate_post-AGB*", + "Candidate_BSS", + "Candidate_WD*", + "Candidate_NS", + "Candidate_BH", + "Candidate_SN*", + "Candidate_low-mass*", + "Candidate_brownD*", + "multiple_object", + "Region", + "Void", + "SuperClG", + "ClG", + "GroupG", + "Compact_Gr_G", + "Gr_QSO", + "PairG", + "IG", + "GlCl?", + "Cl*", + "GlCl", + "OpCl", + "Assoc*", + "**", + "EB*", + "EB*Algol", + "EB*betLyr", + "EB*WUMa", + "EB*Planet", + "SB", + "CataclyV*", + "DQHer", + "AMHer", + "Nova-like", + "Nova", + "DwarfNova", + "XB", + "LMXB", + "HMXB", + "***", + "ISM", + "PartofCloud", + "PN?", + "ComGlob", + "Bubble", + "EmObj", + "Cloud", + "GalNeb", + "BrNeb", + "DkNeb", + "RfNeb", + "MolCld", + "Globule", + "denseCore", + "HVCld", + "BiNeb", + "GasNeb", + "HII", + "PN", + "HIshell", + "SNR?", + "SNR", + "Circumstellar", + "outflow?", + "Outflow", + "OutflowJet", + "HH", + "Star", + "*inCl", + "*inNeb", + "*inAssoc", + "*in**", + "V*?", + "Pec*", + "HB*", + "YSO", + "Em*", + "Be*", + "BlueStraggler", + "RGB*", + "AGB*", + "C*", + "S*", + "RSG*", + "post-AGB*", + "WD*", + "pulsWD*", + "low-mass*", + "brownD*", + "OH/IR", + "CH", + "pMS*", + "TTau*", + "WR*", + "NS*", + "BH*", + "PM*", + "near*", + "HV*", + "V*", + "Irregular_V*", + "Orion_V*", + "Rapid_Irreg_V*", + "Eruptive*", + "Flare*", + "FUOr", + "Erupt*RCrB", + "RotV*", + "RotV*alf2CVn", + "RotV*Ell", + "Pulsar", + "BYDra", + "RSCVn", + "PulsV*", + "RRLyr", + "Cepheid", + "PulsV*delSct", + "PulsV*RVTau", + "PulsV*WVir", + "PulsV*bCep", + "deltaCep", + "gammaDor", + "LPV*", + "Mira", + "semi-regV*", + "SN", + "Symbiotic*", + "Sub-stellar", + "Planet?", + "ExG*", + "Galaxy", + "EllipticalG", + "SpiralG", + "DwarfG", + "IrregG", + "PartofG", + "GinCl", + "BClG", + "GinGroup", + "GinPair", + "High_z_G", + "AbsLineSystem", + "Ly-alpha_ALS", + "DLy-alpha_ALS", + "metal_ALS", + "Ly-limit_ALS", + "Broad_ALS", + "RadioG", + "HII_G", + "LSB_G", + "AGN_Candidate", + "QSO_Candidate", + "Blazar_Candidate", + "BLLac_Candidate", + "EmG", + "StarburstG", + "BlueCompG", + "LensedImage", + "LensedG", + "LensedQ", + "AGN", + "LINER", + "Seyfert", + "Seyfert_1", + "Seyfert_2", + "Blazar", + "BLLac", + "OVV", + "QSO", + "GSN", + "Solar_System", + "Planet", + "Mercury", + "Venus", + "Earth", + "Moon", + "Mars", + "Jupiter", + "Saturn", + "Uranus", + "Neptune", + "PMoon", + "PRing", + "DwarfPlanet", + "Pluto", + "Asteroid", + "Comet", + "KBO", + "Calib", + "Calib_S", + "Calib_aS", + "Calib_phS", + "Calib_sS", + "Cal_polS", + "Cal_spS", + "Cal_rvS", + "Cal_Flat", + "Cal_SFlat", + "Cal_DFlat", + "Cal_Guide*", +] +"""The target type.""" From 5ed2ef36ddde5b3b7bc258a5cba895b53bbf64ca Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 12 Sep 2025 17:27:11 +0200 Subject: [PATCH 015/171] Update the Block model to use the Constraints model --- src/aeonlib/salt/models/block_models.py | 2 +- tests/salt/models/conftest.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 21c8e15..e1a5c41 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -101,7 +101,7 @@ class Block(BaseModel): num_visits: PositiveInt max_num_visits: PositiveInt | None = None min_nights_between_visits: NonNegativeInt = 0 - constraints: None + constraints: Constraints windows: list[Window] | None = None target: SaltSiderealTarget acquisition: None diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index f317d86..7771623 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -17,14 +17,14 @@ def base_request(base_block): @pytest.fixture() -def base_block(base_target): +def base_block(base_constraints, base_target): """A simple block to build or edit from.""" return Block( name="Test", priority=1, ranking="high", num_visits=1, - constraints=None, + constraints=base_constraints, target=base_target, acquisition=None, instrument=None, From 57a3abe9b28437646d9c7ecf67d01834e95deed6 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 12 Sep 2025 17:32:57 +0200 Subject: [PATCH 016/171] Fix minor language issues --- src/aeonlib/salt/validators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeonlib/salt/validators.py b/src/aeonlib/salt/validators.py index 8329334..45374d5 100644 --- a/src/aeonlib/salt/validators.py +++ b/src/aeonlib/salt/validators.py @@ -19,7 +19,7 @@ def _check_le(a: Any, b: Any) -> None: def GreaterEqual(value: Any): """ - Return a Pydantic validator for checking a greater or equal than relation. + Return a Pydantic validator for checking a greater than or equal to relation. The returned validator can be used in a type annotation:: @@ -49,7 +49,7 @@ class DummyModel(pydantic.BaseModel): def LessEqual(value: Any): """ - Return a Pydantic validator for checking a less or equal than relation. + Return a Pydantic validator for checking a less than or equal to relation. The returned validator can be used in a type annotation:: From d0e66bcf4c3211422e7575629fe315292fdc1420 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 12 Sep 2025 17:52:16 +0200 Subject: [PATCH 017/171] Add validators for greater than and less than relations --- src/aeonlib/salt/validators.py | 84 +++++++++++++++++++++++++++++++--- tests/salt/test_validators.py | 46 ++++++++++++++++++- 2 files changed, 123 insertions(+), 7 deletions(-) diff --git a/src/aeonlib/salt/validators.py b/src/aeonlib/salt/validators.py index 45374d5..8757f58 100644 --- a/src/aeonlib/salt/validators.py +++ b/src/aeonlib/salt/validators.py @@ -5,18 +5,60 @@ from pydantic import AfterValidator +def _check_gt(a: Any, b: Any) -> Any: + if a <= b: + raise ValueError(f"{a} is not greater than to {b}.") + return a + + def _check_ge(a: Any, b: Any) -> Any: if a < b: raise ValueError(f"{a} is not greater than or equal to {b}.") return a +def _check_lt(a: Any, b: Any) -> None: + if a >= b: + raise ValueError(f"{a} is not less than to {b}.") + return a + + def _check_le(a: Any, b: Any) -> None: if a > b: raise ValueError(f"{a} is not less than or equal to {b}.") return a +def GreaterThan(value: Any): + """ + Return a Pydantic validator for checking a greater than relation. + + The returned validator can be used in a type annotation:: + + import pydantic + + class DummyModel(pydantic.BaseModel): + duration: Annotated[float, GreaterThan(4)] + + Pydantic will first perform its own internal validation and then check whether + the field value is greater than the argument passed to `GreaterThan` (4 in the + example above). + + It is up to the user to ensure that the field value and the argument of + `GreaterThan` can be compared. + + Parameters + ---------- + value + Value against which to compare. + + Returns + ------- + A validator for checking a greater than relation. + """ + return AfterValidator(lambda v: _check_gt(v, value)) + + def GreaterEqual(value: Any): """ Return a Pydantic validator for checking a greater than or equal to relation. @@ -28,9 +70,9 @@ def GreaterEqual(value: Any): class DummyModel(pydantic.BaseModel): duration: Annotated[float, GreaterEqual(4)] - Pydantic will then first perform its own internal validation and then check - whether the field value is greater than or equal to the argument passed to - `GreaterEqual` (4 in the example above). + Pydantic will first perform its own internal validation and then check whether + the field value is greater than or equal to the argument passed to `GreaterEqual` + (4 in the example above). It is up to the user to ensure that the field value and the argument of `GreaterEqual` can be compared. @@ -47,6 +89,36 @@ class DummyModel(pydantic.BaseModel): return AfterValidator(lambda v: _check_ge(v, value)) +def LessThan(value: Any): + """ + Return a Pydantic validator for checking a less than relation. + + The returned validator can be used in a type annotation:: + + import pydantic + + class DummyModel(pydantic.BaseModel): + height: Annotated[float, LessThan(4)] + + Pydantic will first perform its own internal validation and then check whether + the field value is less than or equal to the argument passed to `LessEqual` (4 in + the example above). + + It is up to the user to ensure that the field value and the argument of + `LessThan` can be compared. + + Parameters + ---------- + value + Value against which to compare. + + Returns + ------- + A validator for checking a less than relation. + """ + return AfterValidator(lambda v: _check_lt(v, value)) + + def LessEqual(value: Any): """ Return a Pydantic validator for checking a less than or equal to relation. @@ -58,9 +130,9 @@ def LessEqual(value: Any): class DummyModel(pydantic.BaseModel): height: Annotated[float, LessEqual(4)] - Pydantic will then first perform its own internal validation and then check - whether the field value is less than or equal to the argument passed to - `LessEqual` (4 in the example above). + Pydantic will first perform its own internal validation and then check whether + the field value is less than or equal to the argument passed to `LessEqual` (4 in + the example above). It is up to the user to ensure that the field value and the argument of `LessEqual` can be compared. diff --git a/tests/salt/test_validators.py b/tests/salt/test_validators.py index f71c74b..d87e175 100644 --- a/tests/salt/test_validators.py +++ b/tests/salt/test_validators.py @@ -4,23 +4,49 @@ import pytest from pydantic import BaseModel, ValidationError -from aeonlib.salt.validators import GreaterEqual, LessEqual +from aeonlib.salt.validators import GreaterEqual, GreaterThan, LessEqual, LessThan + + +class GreaterThanModel(BaseModel): + a: Annotated[int, GreaterThan(4)] class GreaterEqualModel(BaseModel): a: Annotated[int, GreaterEqual(4)] +class LessThanModel(BaseModel): + a: Annotated[int, LessThan(4)] + + class LessEqualModel(BaseModel): a: Annotated[int, LessEqual(4)] class TestValidators: + @pytest.mark.parametrize( + "a, expectation", + [ + (3, pytest.raises(ValidationError)), + (4, pytest.raises(ValidationError)), + (5, nullcontext()), + ], + ) + def test_greater_than(self, a, expectation): + """Test that the GreaterThan validator validates correctly.""" + with expectation: + GreaterThanModel(a=a) + + def test_greater_than_does_not_change_field_value(self): + """Test that the field value is not changed by the GreaterThan validator.""" + assert GreaterThanModel(a=7).a == 7 + @pytest.mark.parametrize( "a, expectation", [(3, pytest.raises(ValidationError)), (4, nullcontext()), (5, nullcontext())], ) def test_greater_equal(self, a, expectation): + """Test that the GreaterEqual validator validates correctly.""" with expectation: GreaterEqualModel(a=a) @@ -28,11 +54,29 @@ def test_greater_equal_does_not_change_field_value(self): """Test that the field value is not changed by the GreaterEqual validator.""" assert GreaterEqualModel(a=7).a == 7 + @pytest.mark.parametrize( + "a, expectation", + [ + (3, nullcontext()), + (4, pytest.raises(ValidationError)), + (5, pytest.raises(ValidationError)), + ], + ) + def test_less_than(self, a, expectation): + """Test that the LessThan validator validates correctly.""" + with expectation: + LessThanModel(a=a) + + def test_less_than_does_not_change_field_value(self): + """Test that the field value is not changed by the LessThan validator.""" + assert LessThanModel(a=2).a == 2 + @pytest.mark.parametrize( "a, expectation", [(3, nullcontext()), (4, nullcontext()), (5, pytest.raises(ValidationError))], ) def test_less_equal(self, a, expectation): + """Test that the LessEqual validator validates correctly.""" with expectation: LessEqualModel(a=a) From dedf48181ebebbb0cfc29ca2718356544c32964a Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 15 Sep 2025 19:16:12 +0200 Subject: [PATCH 018/171] Add the acquisitions model --- src/aeonlib/salt/models/block_models.py | 41 ++++++++++++++++++++-- src/aeonlib/salt/models/types/__init__.py | 3 +- src/aeonlib/salt/models/types/filters.py | 28 +++++++++++++++ tests/salt/data/dummy_finder_chart.pdf | Bin 0 -> 8449 bytes tests/salt/models/conftest.py | 13 +++++++ tests/salt/models/test_block_models.py | 6 ++++ 6 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 src/aeonlib/salt/models/types/filters.py create mode 100644 tests/salt/data/dummy_finder_chart.pdf diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index e1a5c41..21a56ed 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -7,6 +7,7 @@ import astropy.units as u from pydantic import ( BaseModel, + FilePath, NonNegativeInt, NonNegativeFloat, PositiveInt, @@ -16,8 +17,8 @@ from aeonlib.models import Angle, Window from aeonlib.salt.models import SaltSiderealTarget -from aeonlib.salt.models.types import SkyTransparency -from aeonlib.salt.validators import GreaterEqual, LessEqual +from aeonlib.salt.models.types import SalticamFilter, SkyTransparency +from aeonlib.salt.validators import GreaterEqual, GreaterThan, LessEqual class Block(BaseModel): @@ -157,3 +158,39 @@ class Constraints(BaseModel): Angle, GreaterEqual(0 * u.deg), LessEqual(180 * u.deg) ] max_seeing: PositiveFloat + + +class Acquisition(BaseModel): + """ + An acquisition. + + By default, SALT acquisitions are taken with a Johnson V filter and a (nominal) + exposure time of 1 second, but you may choose a different filter or explicitly set + an exposure time. + + The acquisition image is not taken in focus. If you require an in-focus image as + well, you must explicitly request it. + + A finder chart will automatically be generated during submission. However, you may + include additional finder charts, for example if your target is a transient and + hence will not show on the automatically generate finder charts. + + Attributes + ---------- + finder_charts + Additional finder charts. The specified files must exist. + filter + Filter to use for the acquisition. Any Salticam filter may be used. + exposure_time + Exposure time to use for the acquisition. + reference_star + Reference star on which to acquire. This is only needed if acquiring on the + target itself is unfeasible. + include_in_focus_image + Whether an in-focus acquisition image is required. + """ + finder_charts: list[FilePath] + filter: SalticamFilter = "Johnson V" + exposure_time: float = 1. + reference_star: None + include_focused_image: bool = False diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py index 0b5245a..c610787 100644 --- a/src/aeonlib/salt/models/types/__init__.py +++ b/src/aeonlib/salt/models/types/__init__.py @@ -1,4 +1,5 @@ from .block import SkyTransparency +from .filters import SalticamFilter from .target import MagnitudeBandpass, TargetType -__all__ = ["MagnitudeBandpass", "SkyTransparency", "TargetType"] +__all__ = ["MagnitudeBandpass", "SalticamFilter", "SkyTransparency", "TargetType"] diff --git a/src/aeonlib/salt/models/types/filters.py b/src/aeonlib/salt/models/types/filters.py new file mode 100644 index 0000000..db5eaee --- /dev/null +++ b/src/aeonlib/salt/models/types/filters.py @@ -0,0 +1,28 @@ +from typing import Literal + +SalticamFilter = Literal[ + "Fused silica clear", + "Johnson U", + "Johnson B", + "Johnson V", + "Cousins R", + "Cousins I", + "380nm 40nm FWHM", + "SDSS u'", + "SDSS g'", + "SDSS r'", + "SDSS i'", + "SDSS z'", + "H-alpha", + "H-beta narrow", + "H-beta wide", + "Stroemgren u", + "Stroemgren v", + "Stroemgren b", + "Stroemgren y", + "SRE 1", + "SRE 2", + "SRE 3", + "SRE 4", +] +"""A filter for the Salticam detector.""" diff --git a/tests/salt/data/dummy_finder_chart.pdf b/tests/salt/data/dummy_finder_chart.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3a6e3e757b6a2198fc0d14d8e71f561426c22202 GIT binary patch literal 8449 zcmcIqXIN8Pv!*E>qzDKI5vfu_AOWI+^dca=N-=~0p&LR|X-bhUO`3@Gjz|%tN$-L* z0TGlg(xo@KJ9y4_Jng&p&rNu;_nI|pX7;Ri?`PiGaB0db3POZL$+%kPzRxY?Y`%Ek z(ncl<5(YV0*pgkp4gxEp?Xf5faI{BYQSvCHlO+lSRzo>jV{Jg9BEldkDKZx<28D1S z^T3aL^3Jwd_?-WmcL@ArP**h4h}K_vX7>GnwA>lVShlc?C-mc-ugSgo%?<@j+e!DA z(qstG2zR&oF^dR6iHm5?P$*U)K`i&8nbsvW7Qb4*(G>2SdM`N@!p=0_>ZY0*HD_db zn3m>@(VHgPDbYYV|FSKFEGC1dEe2a@MrBwSF8qX4Rn3y4#kdTgk<1p9Fx}GoqUxs%ZW@}d=wWm>28RFlJt(yi+(_TTVio85~F7;Frd z&SFK0Jc7JhhN8#UQ89YgA`WYhd&rjRjr+`t9}&J63?wp8i}AdjEs`{`%BbK|n#&%hGDDghzYkyn8$$?ukXpwAp-{bO}OJEO&~z^jb1S459;& zN{@xo_QCGeX)L^PzLe|LxbXyn@z~C?;5ebk46e1)6Hzfo1?>BF_)xYn$ zCdLV=gTjJt11{wiL10~!2Nq-s0?RqsJ7IL35l9pW2M#$G2ncrSL`n*P2;lcrtg8zF ziQ&Y6bsRu`b0G1HgIhpwU>O{=906Q`!15?JG!mt)B>Nw5iHJc&{uzG1LN4X0yROa{ zLr`Cxc-?zi4iMKC^F30~z%}LbL|VZrzM2Gq{@iZzXB6pa`1CDx8}OBaIFJhTbRR4193n5*1YwzDeaYgscc@K%%C<->_j6o4h9KEidhNa_0GKIuqi#XVaLC zdN0&MpNG|GP9||pWrPu>nIv$1vCeovjH=UBNy3}k=LlwFYw|S1|k!?L2LKLiGkxes_fS&u>VRf)krn7GW;g4-rpL*<} z*0)to5h2Xz`_k#D0zQ|KL!wR8N)hYIiqVI8oBg+ApV_>1jwL(p9qMgxFZVr{343nz z*?zeYT*rX7!)cs+v8+B$53k(etn}Pep~e~r@B10N^2ukLY=QP>(Jj*yJmjIv%l0n# zsdhsAU&RkoA3Alt4TSF|cCQ}lU(05^;dfc(yeLXxE;}b%RlcW(f-1hx@_6^$$xH(r z{jRfF;8|(|<3_+&J*}^?r2fG+H38TC44(*DlKJ}#Px&y3@ep6|PO+o6aW%*D3V>@? zsStNKN#e;}CggOyagV@j>GnAYSx0@XO;OZKmq?L&KNb$y$xT8Wr_^LPbmn&`A}ujo z=>iurKKt7@63{*&#=anf`UwdW2;8A$^%Zp{oCM+d`zoI!d`c*!On{aqDwUBM#Uqn} zjN-dJvUSEA^1Jt*=+;9q=d&h61`U+X1k_erS@1>*vm=%j7>{$fQn` z{cw)On8Z^~MpJ^GuvISj$+>9h{rB9tOjjP|r^#tbZe7MQSCg1NI(R|+9t1smjPK=7 zlRz+tpd7_}?K@U-jg>^Hsr!q($N6W(j1A*cH>((=s4ANnr&!kU1Om*PVo0PrNC^8C z4M|uwh3Jz*`PE;0uw{qg_$6uuqg|D#0QI; z!ovy31bsEec`>VVqJH}ivn+;RWgGGo-f$xoqe%`IY|+0HdFOUX{42sy#PT&(s`iEp z)2z#*kJ>Af^lJ~P8(r2d9$cy?^I`R2`y9#M_Lk=f zsWFk=L+VRUL7euSy62cl7D$f$X_}x~ez_W0j=*!CEhZLxuOdceYE@XdZ^h871adNJ zbMhsY`{}CKa+q>Ua@Spo>AY&Ggkdt0Hq$8POa+;9QAgjiA`p=%*=8l{ZNSs3;_*$5}kKNU{jFcr*=b)#9p+_KU8h)yR`x zEE(X+y2LCynB_T8Rjyus^&{$|@I@u@%)`N$#nvtMuXS6bC@$3PIo>(eIp(>tF72~T z!guyWwJ&6a#e~&_MQ%dp-}KG&jf=fRo1N71#qD2lyz(H9BhD%gKQ1y~P0wkVemEuH zAzx!i*p}NCVLN7fI4o2U-j~zo{=6^69C|W)6!fj6I{04If$etlH_2}?%t_4k%omvT znB$>sunS3!Nu5b^Nej^SavNjv3L@h<wooUe1py=)aO-oN=v90{QxG$cEI+ZSIob6qRU|@J@A|(V&>2+uPDkB{OtC2#p_yzk^IiFLEdii=Odo#!>7}ptx1eq}X zcc-dYF;PO6LSg0}&S^t><8d>_O9wsbBjF2fEeIn znj2vnZNe~s1 zekIE!QY6wRmLeA+r9IQSo4q_JYddgGQ>b2dnD%;aGexJVS4_+g-J*NTFAZZ zS*7RU?cq)&XrUl4*HWV#*)hrYk*bF|=y#Bm*X#euP@~qAuLzjg&tnjqf3|Wk5c1x zF|w*ji1?wltsKUg%=O5Kyv(+A(pm|X+NIS)Gru*PH0#)Xv2(NY&>Ln2b9c3_cl!}| z)CgO(qbdscP;4^(=9ar@WA)T_{Zga@$U%8*y<4$69L4h4F=TQ`>xEWH{T-XKwfQ#L zH?mq&b%MROsq9mJD^>15rPlrrvaD9qi`fTK7us_kW6>N{nNx^^`>E>g^}6V`{FOAMRPsh)4x~N7%bQ8BW~ZlLyeBKj4T()Ev>ux(pG6W z=Ue7CKjz4!`MPiSD_2rJwvUgz{p{c~h#yYaN-V`j&syu#v8!oUB;(ME_%v16+_#ke zt@^FCkGYThit+rRq5%FB{Sh1$?=(blgciWwgzii9hrz=?06wP?E zM-WYX#%UiueF%P?DTcnUcK`FD?_kQs-iv}a zl20CNh3zCJm33LG_C@ucf0hhSb@%=*+ZM198q64U)c`A1T|PUIQe09z+3gqq(rn`m zCU3m%@JKJdz`7W2dd(}vt8OLmIQ#=!&GV<`%iie4iZzG2Mdz~Pr!z$n^=G^m4onZh zlRMR3a}g&gEQFzF4_1Zh@1K}`l3etfJnkpXqc*$Ib5G*f<`}s=lg>k?apigbjiTe> zy%DRtx~=6GBX|1B9$QBSR>^@+;`U!^G@t3q>*H4}SNx__q|_I-A2xCC)mH0zcUb)3 zOu$>dAFp@g`U_IM-4E-mEjnA)?fRDzZ`_>Bt%o0c*l}2}{5m`6j$X^o37IUYzp>+Y zoOX!ZYL8l6*4WdCOAWu_{($u8y9c*})%@pO|INd{m)W=q7FYNJ#jc8+oGiixWeLL7 zs@fpa-%D2ru5$jpIR06R>tHdiNUSafh0=C%!h*o6Ao%GTphm}4=eP+F1XsZW6}XE0 z&*5ps{zpNKTkuat73@(CAg~Tl6+0na9dM;SSQB&_0$@uOD9Rx}D|=JWFV*#_?7v#m z#F_a$NNdnpp|EHq;uZ>s z>y-G&qg;>}v@_NTLk9UtiNFeu09!^o0@tUGKx#WVARPaYD55bgSUDR61|ZP@R0nBLrGQC=n3klw3OO=Azy@B?WiSd+_V=FhePMy97c2UfW z0*N+T>l=@FRD232092c85AM+6%Vru2(Z6Dl4Z+xN2 zxMluAKp?F#H?)X1L$zH)(t1eHM+v^Y72G_$`iK{nIzwadzM}nD;Cq^{g zo?YKq&a+L~R!ezc_3EQ}tY#`~a$=hgRAa+xH={IgW86sC{j11Z;#qMa6Q6X?b)8Ge zqe9gpKR6k;;+I~Y?1e6FKUtAS#aaJnhu9d&EBX6-%Wv8IM05-H0#vViN&4A`sBpv^>if$%7bG5E= zwyp=Jl|K1FW@cP-_tf0tIYciM(M-QxDI8?EdZCr2(A&%YqN($(EvNC)F9E;hMswgX0T-3!guC(>gn(OyM{ zoaeW|x(Azw+vcaTK=pJGN}dGOOFE)1SsVgdW3-3yyjj6Dybie+Lh8eXU&`c^FWRLK zZGd9MZk)^NP0C8sYse$lp`>l0urLmt>SNmshXj@*qZV4xy-JkK1`)m5_BQj64`Wf27^$uD0`zXsPLCqt?*e zps4QEcRn`7l8Tc>dMEsi9iOO0TEcEIlt!zsnFJlRM_#nS8(rnbXo_~7thMiRZO++t z)2sJiI(WY4XBclLmZ%Ek@9==3 zq((Tw5Io9m#J5Ke3-x-azo7R_MPT?dCo5YkEyla8FMMYTQ_|y*P1%idt0NH{)P+f8>DLG$rdoH9m>~4bi%dVOh_K`jnamVv+ zs(qrw{o|=O-Bhw2KMJuSsV1|fdM4zhlue)1KDjb`hMi#@(HYkHxJc?d-rW~?Y6Ou4 z8u&SW)d3LlI=m?Ykf3fZHIaYggx<4*o)?VUtNFTV0$Wy%o{aMexp!HRSCF~}Gy2nK zKN;Fmx_;7Zr>w;UTa*^Om4$qz^0iNJW^3Xq7HIf-l|%>Eiy-Wfm z6D=!vR|g8^6fHAlWRhs5!6k&F#H?rPDZkoD5>Ih}1Pd)p1Gx%fi^ibkA5y}NDwMCU zoipYXoU5c#t!vIc`cR&!P$=dw8oPv!rypDC+}`45WoVrcn!6X6RcpD+IpqIZ^S$E0 zkDcYm`Qcb%`ieo*1d(^D{f%R>`pcoCpD5pT_ZA(G56FzMySh`hnQH447Vyu9ZEf$3{ni1u$R2gK^~KCfLr_( zt@Rt#9d|TRb*5>8qXJ}4+mhA%NN@XgFgNUf$*#vV*?oG4uMZ);PPSfm0(GXUBB>=X!7nBp zj-%6$;G;2Tv#F3PxM*y&BeSD7C)8nD!l6Q=y`?0eDTz;&?bN}*`?8fEAwgc-)O;uCOfu_@1T1uV=CYmvQfr4(jnM#bRaQbS5 zDW%INVRe}xN~(zx^E-O}r8Fqy*?OmJ8;6Sy>r(EH&C>WV0dj1_cf1Jv;Zdf^{LTBr z1Wrsu3fIKcWR5V$)X_Z84Vw4;-Pa}qEz<0&nz>qbxw3YpA?9qMH`AsFo3nH*?C#e$ zFR9Z{c5D-g$VK&N=ezd@+6fa+#A-tUy_eH+98gAqsrG)4#`JCOEu_?ImL;kgs*=kaUXW8v^ zx8SnU$F_ug=H|NxeXWzl78(u3xl)%NZF2NfQ?Tq8Hdzv=vtU(9dMLURuJX5sDy-cR zkaM2zvy<@~1NZf*20eYs;OG?rR@c0%l;b|=i7X5j3EXb=6r<3Y8T)dxjZJ7$(n{2T z+c3R>gUW<@ll01+k@&=F@C`A+x4|jao6(kUw)g9XuKyrupWa)+N0VFh2YDUc3m8b~ zZx?N%kuq##n~C0sG;5(ds4_1&ZoYW;NNmS(Nu4t<@=K);o&@-)O043WPs-jhakig` zGlc}__j(^T3b+y+H$XMdM&MifZZ>dz zfql8MeyQPscti3tM78D>X~NmY)fe8W!l?ad{Qp%Ty)ftoDUPzoDc7~cU-(x&YMCL33UqDT2oU9sWa|VLua=$s=gU7 zCH)MpZog*wZevQVP|0Mh$WH4*$PlDN<#v#q$--kP2-@Ad`hDd= zih}g?VX%k6LDBOCSuEoGg;C`KN<%WAH`AyMW6$T|h3(=xJ2iw{pFhE=swiIcdg;~c zeXpoz`&2$1FRVX679dtXAop5^N}q?!0xySB|GjVYlDgf4^2E;U1uTrmZr{Xg=2-~G z+X`R)VDZ$piC$*XA01K3xoJgdkRb_pokppEK#@7XM_M0`#&F$7z`jPAS=-CGAOQVf%||Qf0v1h z2?Lz>&oW_Q91s4B3?}mTwK!J%7d=r4z~f(K;y4cd7kgqNKvU( Date: Tue, 16 Sep 2025 10:49:17 +0200 Subject: [PATCH 019/171] Fix a formatting issue --- src/aeonlib/salt/models/block_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 21a56ed..4d2c497 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -189,8 +189,9 @@ class Acquisition(BaseModel): include_in_focus_image Whether an in-focus acquisition image is required. """ + finder_charts: list[FilePath] filter: SalticamFilter = "Johnson V" - exposure_time: float = 1. + exposure_time: float = 1.0 reference_star: None include_focused_image: bool = False From 057482e5b7e1ff94c33fdb888dcb19d3eff84b51 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 16 Sep 2025 11:50:36 +0200 Subject: [PATCH 020/171] Add a type annotation for defining custom types based on an AstroPy Quantity --- src/aeonlib/salt/models/types/__init__.py | 9 ++- src/aeonlib/salt/models/types/quantity.py | 90 +++++++++++++++++++++++ tests/salt/types/test_quantity.py | 89 ++++++++++++++++++++++ 3 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/aeonlib/salt/models/types/quantity.py create mode 100644 tests/salt/types/test_quantity.py diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py index c610787..5d6f8cc 100644 --- a/src/aeonlib/salt/models/types/__init__.py +++ b/src/aeonlib/salt/models/types/__init__.py @@ -1,5 +1,12 @@ from .block import SkyTransparency from .filters import SalticamFilter +from .quantity import AstropyQuantityTypeAnnotation from .target import MagnitudeBandpass, TargetType -__all__ = ["MagnitudeBandpass", "SalticamFilter", "SkyTransparency", "TargetType"] +__all__ = [ + "AstropyQuantityTypeAnnotation", + "MagnitudeBandpass", + "SalticamFilter", + "SkyTransparency", + "TargetType", +] diff --git a/src/aeonlib/salt/models/types/quantity.py b/src/aeonlib/salt/models/types/quantity.py new file mode 100644 index 0000000..bb77cce --- /dev/null +++ b/src/aeonlib/salt/models/types/quantity.py @@ -0,0 +1,90 @@ +import dataclasses +from typing import Any + +from astropy.units import UnitBase, Quantity +from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler +from pydantic.json_schema import JsonSchemaValue +from pydantic_core import core_schema + + +@dataclasses.dataclass(frozen=True) +class AstropyQuantityTypeAnnotation: + """ + Annotation for defining custom Pydantic types based on a `astropy.units.Quantity`. + + To define such a custom type, instantiate `AstropyQuantityTypeAnnotation` with the + default unit `d` and pass it as a type annotation. Pydantic fields with this type can + be instantiated wth a `float` or a `astropy.units.Quantity` with units that are + compatible with `d`. If a `float` is used, it is assumed to be given with `d` as the + unit. The field is stored as a `astropy.units.Quantity` with unit `d`. + + For example, you can define a ProperMotion type as follows: + + ``` + from typing import Annotated, Union + from astropy import units as u + from astropy.units import Quantity + from aeonlib.salt.models.types import AstropyQuantityTypeAnnotation + + ProperMotion = Annotated[Union[Quantity, float], AstropyQuantityTypeAnnotation(u.arcsec / u.year)] + ``` + + This type can then be used in a Pydantic model: + + ``` + from pydantic import BaseModel + + class MovingObject(BaseModel): + proper_motion: ProperMotion + + # Create the same object in three different ways. + # Note: 1 year = 8766 hours + object1 = MovingObject(proper_motion=8766) # 3 arcsec per year + object2 = MovingObject(proper_motion=8766 * u.arcsec / u.year) + object3 = MovingObject(proper_motion=1 * u.arcsec / u.hour) + """ + + # Based on + # https://docs.pydantic.dev/latest/concepts/types/#handling-third-party-types + + default_unit: UnitBase + + def __get_pydantic_core_schema__( + self, + _source_type: Any, + _handler: GetCoreSchemaHandler, + ) -> core_schema.CoreSchema: + def validate_from_float(value: float) -> Quantity: + return Quantity(value, unit=self.default_unit) + + def validate_from_quantity(value: Quantity) -> Quantity: + return value.to(self.default_unit) + + from_float_schema = core_schema.chain_schema( + [ + core_schema.float_schema(), + core_schema.no_info_plain_validator_function(validate_from_float), + ] + ) + + from_quantity_schema = core_schema.chain_schema( + [ + core_schema.is_instance_schema(Quantity), + core_schema.no_info_plain_validator_function(validate_from_quantity), + ] + ) + + return core_schema.json_or_python_schema( + json_schema=from_float_schema, + python_schema=core_schema.union_schema( + [from_quantity_schema, from_float_schema] + ), + serialization=core_schema.plain_serializer_function_ser_schema( + lambda instance: instance.to(self.default_unit).value + ), + ) + + def __get_pydantic_json_schema( + self, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + return handler(core_schema.float_schema()) diff --git a/tests/salt/types/test_quantity.py b/tests/salt/types/test_quantity.py new file mode 100644 index 0000000..33c0538 --- /dev/null +++ b/tests/salt/types/test_quantity.py @@ -0,0 +1,89 @@ +import json +from typing import Annotated, Union + +import pytest +from astropy import units as u +from astropy.units import Quantity +from pydantic import BaseModel + +from aeonlib.salt.models.types.quantity import AstropyQuantityTypeAnnotation + +Wavelength = Annotated[ + Union[Quantity, float], AstropyQuantityTypeAnnotation(u.Angstrom) +] + +ProperMotion = Annotated[ + Union[Quantity, float], AstropyQuantityTypeAnnotation(u.arcsec / u.year) +] + + +class CelestialObject(BaseModel): + peak_wavelength: Wavelength + proper_motion: ProperMotion + + +class TestAstropyQuantityTypeAnnotation: + @pytest.mark.parametrize( + "peak_wavelength, proper_motion", + [ + # 1 year = 8766 hours + (4107 * u.Angstrom, 4383 * u.arcsec / u.year), + (410.7 * u.nm, 0.5 * u.arcsec / u.hour), + ], + ) + def test_from_quantity(self, peak_wavelength, proper_motion): + """ + Test objects constructed from astropy Quantity objects dump to json as floats + """ + asteroid = CelestialObject( + peak_wavelength=peak_wavelength, proper_motion=proper_motion + ) + dumped = asteroid.model_dump_json() + assert pytest.approx(json.loads(dumped)) == { + "peak_wavelength": 4107.0, + "proper_motion": 4383.0, + } + + def test_from_float(self): + """Test objects constructed from floats dump to json as floats""" + t = CelestialObject(peak_wavelength=7567.6, proper_motion=0.98) + dumped = t.model_dump_json() + assert pytest.approx(json.loads(dumped)) == { + "peak_wavelength": 7567.6, + "proper_motion": 0.98, + } + + @pytest.mark.parametrize( + "peak_wavelength, proper_motion", + [ + # 1 year = 8766 hours + (4107 * u.Angstrom, 4383 * u.arcsec / u.year), + (410.7 * u.nm, 0.5 * u.arcsec / u.hour), + ], + ) + def test_quantity_attributes(self, peak_wavelength, proper_motion): + """Test quantities are accessible on the model""" + asteroid = CelestialObject( + peak_wavelength=peak_wavelength, proper_motion=proper_motion + ) + assert isinstance(asteroid.peak_wavelength, Quantity) + assert pytest.approx(asteroid.peak_wavelength.value) == 4107 + assert asteroid.peak_wavelength.unit == u.Angstrom + assert pytest.approx(asteroid.proper_motion.value) == 4383.0 + assert asteroid.proper_motion.unit == u.arcsec / u.year + + def test_from_json(self): + """Test models can be constructed from json""" + target_json = json.dumps( + { + "peak_wavelength": "5516.89", + "proper_motion": "0.076", + } + ) + target = CelestialObject.model_validate_json(target_json) + assert isinstance(target.peak_wavelength, Quantity) + assert pytest.approx(target.peak_wavelength.value) == 5516.89 + assert target.peak_wavelength.unit == u.Angstrom + assert isinstance(target.proper_motion, Quantity) + assert pytest.approx(target.proper_motion.value) == 0.076 + assert target.proper_motion.unit == u.arcsec / u.year From 59cf06e4efd631a9fe30cacfb6f9cac7c2297e3a Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 16 Sep 2025 17:29:39 +0200 Subject: [PATCH 021/171] Add types for a duration --- src/aeonlib/salt/models/types/duration.py | 11 ++++++ tests/salt/types/test_duration.py | 43 +++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/aeonlib/salt/models/types/duration.py create mode 100644 tests/salt/types/test_duration.py diff --git a/src/aeonlib/salt/models/types/duration.py b/src/aeonlib/salt/models/types/duration.py new file mode 100644 index 0000000..b91584b --- /dev/null +++ b/src/aeonlib/salt/models/types/duration.py @@ -0,0 +1,11 @@ +from typing import Annotated, Union + +from astropy import units as u +from astropy.units import Quantity + +from aeonlib.salt.models.types import AstropyQuantityTypeAnnotation +from aeonlib.salt.validators import GreaterThan + +Duration = Annotated[Union[Quantity, float], AstropyQuantityTypeAnnotation(u.s)] + +PositiveDuration = Annotated[Duration, GreaterThan(0 * u.s)] diff --git a/tests/salt/types/test_duration.py b/tests/salt/types/test_duration.py new file mode 100644 index 0000000..fb3027c --- /dev/null +++ b/tests/salt/types/test_duration.py @@ -0,0 +1,43 @@ +from contextlib import nullcontext + +import pytest + +from astropy import units as u +from pydantic import ValidationError, BaseModel + +from aeonlib.salt.models.types import Duration, PositiveDuration + + +class A(BaseModel): + a: Duration + + +class B(BaseModel): + b: PositiveDuration + + +class TestDuration: + @pytest.mark.parametrize("a", [5, 5 * u.s]) + def test_duration(self, a): + """Test that durations are stored with a unit.""" + v = A(a=a) + assert v.a.value == 5 + assert v.a.unit == u.s + + +class TestPositiveDuration: + @pytest.mark.parametrize( + "b, expectation", + [ + (-5.6, pytest.raises(ValidationError)), + (-3 * u.s, pytest.raises(ValidationError)), + (0, pytest.raises(ValidationError)), + (0 * u.s, pytest.raises(ValidationError)), + (0.001, nullcontext()), + (67 * u.s, nullcontext()), + ], + ) + def test_positive_duration_must_be_positive(self, b, expectation): + with expectation: + B(b=b) + assert True From 7602f3768ca08b61c83de8fc3de078fe773508fa Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 16 Sep 2025 17:31:45 +0200 Subject: [PATCH 022/171] Type the exposure time as a positive duration --- src/aeonlib/salt/models/block_models.py | 6 +++--- src/aeonlib/salt/models/types/__init__.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 4d2c497..d594a15 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -17,8 +17,8 @@ from aeonlib.models import Angle, Window from aeonlib.salt.models import SaltSiderealTarget -from aeonlib.salt.models.types import SalticamFilter, SkyTransparency -from aeonlib.salt.validators import GreaterEqual, GreaterThan, LessEqual +from aeonlib.salt.models.types import PositiveDuration, SalticamFilter, SkyTransparency +from aeonlib.salt.validators import GreaterEqual, LessEqual class Block(BaseModel): @@ -192,6 +192,6 @@ class Acquisition(BaseModel): finder_charts: list[FilePath] filter: SalticamFilter = "Johnson V" - exposure_time: float = 1.0 + exposure_time: PositiveDuration = 1.0 * u.s reference_star: None include_focused_image: bool = False diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py index 5d6f8cc..d99d239 100644 --- a/src/aeonlib/salt/models/types/__init__.py +++ b/src/aeonlib/salt/models/types/__init__.py @@ -1,11 +1,14 @@ +from .quantity import AstropyQuantityTypeAnnotation from .block import SkyTransparency +from .duration import Duration, PositiveDuration from .filters import SalticamFilter -from .quantity import AstropyQuantityTypeAnnotation from .target import MagnitudeBandpass, TargetType __all__ = [ "AstropyQuantityTypeAnnotation", + "Duration", "MagnitudeBandpass", + "PositiveDuration", "SalticamFilter", "SkyTransparency", "TargetType", From 50446c5c8e6132075277562c469ca91766f5c906 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 09:43:35 +0200 Subject: [PATCH 023/171] Refactor the validation for the target right ascension The rationale is that the same validation should be applied to a reference star and hence its code should be as reusable as possible. --- src/aeonlib/salt/models/target_models.py | 15 +++++++-------- src/aeonlib/salt/validators.py | 11 +++++++++++ tests/salt/models/test_target_models.py | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py index 5eb6063..5925140 100644 --- a/src/aeonlib/salt/models/target_models.py +++ b/src/aeonlib/salt/models/target_models.py @@ -4,11 +4,12 @@ from typing import Self -import astropy.units as u -from pydantic import BaseModel, NonNegativeFloat, model_validator +import astropy.coordinates +from pydantic import BaseModel, NonNegativeFloat, field_validator, model_validator from aeonlib.models import SiderealTarget from aeonlib.salt.models.types import MagnitudeBandpass, TargetType +from aeonlib.salt.validators import check_in_visibility_range class SaltSiderealTarget(SiderealTarget): @@ -31,12 +32,10 @@ class SaltSiderealTarget(SiderealTarget): target_type: TargetType magnitude_range: MagnitudeRange - @model_validator(mode="after") - def check_target_viewable(self): - if self.ra < -76 * u.deg or self.ra > 11 * u.deg: - raise ValueError("ra not between -76 and 11 degrees.") - - return self + @field_validator("ra", mode="after") + @classmethod + def check_right_ascension_viewable(cls, value: astropy.coordinates.Angle): + return check_in_visibility_range(value) class MagnitudeRange(BaseModel): diff --git a/src/aeonlib/salt/validators.py b/src/aeonlib/salt/validators.py index 8757f58..5d42c44 100644 --- a/src/aeonlib/salt/validators.py +++ b/src/aeonlib/salt/validators.py @@ -2,6 +2,8 @@ from typing import Any +import astropy.coordinates +from astropy import units as u from pydantic import AfterValidator @@ -147,3 +149,12 @@ class DummyModel(pydantic.BaseModel): A validator for checking a less than or equal to relation. """ return AfterValidator(lambda v: _check_le(v, value)) + + +def check_in_visibility_range( + dec: astropy.coordinates.Angle, +) -> astropy.coordinates.Angle: + if dec < -76 * u.deg or dec > 11 * u.deg: + raise ValueError("Not in SALT's visibility range (between -76 and 11 degrees).") + + return dec diff --git a/tests/salt/models/test_target_models.py b/tests/salt/models/test_target_models.py index 59e1788..cc6958a 100644 --- a/tests/salt/models/test_target_models.py +++ b/tests/salt/models/test_target_models.py @@ -22,7 +22,7 @@ def test_salt_sidereal_target(self, base_target): ], ) def test_ra_range(self, ra, expectation, base_target): - """Test that the right ascension must be between -76 and 11 degrees.""" + """Test that the right ascension must be in SALT's visibility range.""" target = base_target.model_dump() target["ra"] = ra with expectation: From 4a2c04487a691d73ab0b18210debb4cbeb7bc360 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 09:55:46 +0200 Subject: [PATCH 024/171] Fix the target coordinate validation --- src/aeonlib/salt/models/target_models.py | 4 ++-- tests/salt/models/test_target_models.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py index 5925140..3863a47 100644 --- a/src/aeonlib/salt/models/target_models.py +++ b/src/aeonlib/salt/models/target_models.py @@ -32,9 +32,9 @@ class SaltSiderealTarget(SiderealTarget): target_type: TargetType magnitude_range: MagnitudeRange - @field_validator("ra", mode="after") + @field_validator("dec", mode="after") @classmethod - def check_right_ascension_viewable(cls, value: astropy.coordinates.Angle): + def check_declination_viewable(cls, value: astropy.coordinates.Angle): return check_in_visibility_range(value) diff --git a/tests/salt/models/test_target_models.py b/tests/salt/models/test_target_models.py index cc6958a..dbc9119 100644 --- a/tests/salt/models/test_target_models.py +++ b/tests/salt/models/test_target_models.py @@ -13,7 +13,7 @@ def test_salt_sidereal_target(self, base_target): assert True @pytest.mark.parametrize( - "ra, expectation", + "dec, expectation", [ (astropy.coordinates.Angle("-76.001d"), pytest.raises(ValueError)), (astropy.coordinates.Angle("-76d"), nullcontext()), @@ -21,10 +21,10 @@ def test_salt_sidereal_target(self, base_target): (astropy.coordinates.Angle("11.0001d"), pytest.raises(ValueError)), ], ) - def test_ra_range(self, ra, expectation, base_target): - """Test that the right ascension must be in SALT's visibility range.""" + def test_dec_range(self, dec, expectation, base_target): + """Test that the declination must be in SALT's visibility range.""" target = base_target.model_dump() - target["ra"] = ra + target["dec"] = dec with expectation: SaltSiderealTarget(**target) From d4922f5a71eacbc2adf48d817eebe52062d3bb59 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 11:46:32 +0200 Subject: [PATCH 025/171] Add the reference star model --- src/aeonlib/salt/models/block_models.py | 35 +++++++++++++++++++++++-- tests/salt/models/conftest.py | 12 ++++++--- tests/salt/models/test_block_models.py | 23 ++++++++++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index d594a15..32125f8 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -13,12 +13,13 @@ PositiveInt, PositiveFloat, model_validator, + AfterValidator, ) from aeonlib.models import Angle, Window from aeonlib.salt.models import SaltSiderealTarget from aeonlib.salt.models.types import PositiveDuration, SalticamFilter, SkyTransparency -from aeonlib.salt.validators import GreaterEqual, LessEqual +from aeonlib.salt.validators import GreaterEqual, LessEqual, check_in_visibility_range class Block(BaseModel): @@ -193,5 +194,35 @@ class Acquisition(BaseModel): finder_charts: list[FilePath] filter: SalticamFilter = "Johnson V" exposure_time: PositiveDuration = 1.0 * u.s - reference_star: None + reference_star: ReferenceStar | None = None include_focused_image: bool = False + + +class ReferenceStar(BaseModel): + """ + A reference star on which to acquire. + + In case acquiring on the target itself is not possible (as, for example, the target + is too faint), you can specify a reference star. This will be used for the + acquisition instead, and after the acquisition a telescope offset from the + reference star to the actual target is applied. + + The right ascension and declination can be supplied as a `astropy.coordinates.Angle` + instance, a `astropy.units.Quantity` instance, a string in a format understood by + AstroPy or a float in degrees. + + By default, the equinox is assumed to be 2000.0, but you can choose another oner. + + Arguments + --------- + ra + Right ascension of the reference star. + dec + Declination of the reference star. + equinox + Equinox of the coordinates. + """ + + ra: Angle + dec: Annotated[Angle, AfterValidator(check_in_visibility_range)] + equinox: Annotated[float, LessEqual(2100)] = 2000.0 diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 1d58cdf..acc51a4 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -2,6 +2,7 @@ import astropy.coordinates import pytest +from astropy import units as u from aeonlib.salt.models import ( Request, @@ -10,7 +11,7 @@ SaltSiderealTarget, Constraints, ) -from aeonlib.salt.models.block_models import Acquisition +from aeonlib.salt.models.block_models import Acquisition, ReferenceStar @pytest.fixture() @@ -68,6 +69,9 @@ def base_acquisition(): finder_chart = ( pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart.pdf" ) - return Acquisition( - finder_charts=[finder_chart], reference_star=None, include_focused_image=False - ) + return Acquisition(finder_charts=[finder_chart]) + + +@pytest.fixture() +def base_reference_star(): + return ReferenceStar(ra=117.564 * u.deg, dec=-63.9 * u.deg) diff --git a/tests/salt/models/test_block_models.py b/tests/salt/models/test_block_models.py index 481e6a5..f1a2f10 100644 --- a/tests/salt/models/test_block_models.py +++ b/tests/salt/models/test_block_models.py @@ -1,9 +1,11 @@ from contextlib import nullcontext +import astropy.coordinates import pytest from pydantic import ValidationError from aeonlib.salt.models import Block +from aeonlib.salt.models.block_models import ReferenceStar class TestBlock: @@ -47,3 +49,24 @@ class TestAcquisition: def test_acquisition(self, base_acquisition): """Test that acquisitions can be built.""" assert True + + +class TestReferenceStar: + def test_reference_star(self, base_reference_star): + """Test that reference stars can be built.""" + assert True + + @pytest.mark.parametrize( + "dec, expectation", + [ + (astropy.coordinates.Angle("-76.001d"), pytest.raises(ValueError)), + (astropy.coordinates.Angle("-76d"), nullcontext()), + (astropy.coordinates.Angle("11d"), nullcontext()), + (astropy.coordinates.Angle("11.0001d"), pytest.raises(ValueError)), + ], + ) + def test_dec_range(self, dec, expectation, base_reference_star): + ref_star = base_reference_star.model_dump() + ref_star["dec"] = dec + with expectation: + ReferenceStar(**ref_star) From 1af9fd7178fffb50d0c8f5ff5898de0b9f1693c7 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 12:00:21 +0200 Subject: [PATCH 026/171] Add the acquisition model in the block model --- src/aeonlib/salt/models/block_models.py | 8 ++++++-- tests/salt/models/conftest.py | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 32125f8..b86a173 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -18,7 +18,11 @@ from aeonlib.models import Angle, Window from aeonlib.salt.models import SaltSiderealTarget -from aeonlib.salt.models.types import PositiveDuration, SalticamFilter, SkyTransparency +from aeonlib.salt.models.types import ( + PositiveDuration, + SalticamFilter, + SkyTransparency, +) from aeonlib.salt.validators import GreaterEqual, LessEqual, check_in_visibility_range @@ -106,7 +110,7 @@ class Block(BaseModel): constraints: Constraints windows: list[Window] | None = None target: SaltSiderealTarget - acquisition: None + acquisition: Acquisition instrument: None pool: str | None = None data_notification: Literal["normal", "fast"] = "normal" diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index acc51a4..db09801 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -21,7 +21,7 @@ def base_request(base_block): @pytest.fixture() -def base_block(base_constraints, base_target): +def base_block(base_acquisition, base_constraints, base_target): """A simple block to build or edit from.""" return Block( name="Test", @@ -30,7 +30,7 @@ def base_block(base_constraints, base_target): num_visits=1, constraints=base_constraints, target=base_target, - acquisition=None, + acquisition=base_acquisition, instrument=None, ) From 5f83ed7b1f5a735289fa1e8d7eb72cff55f61dd6 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 12:01:18 +0200 Subject: [PATCH 027/171] Add an Instrument type --- src/aeonlib/salt/models/block_models.py | 3 ++- src/aeonlib/salt/models/types/__init__.py | 3 ++- src/aeonlib/salt/models/types/block.py | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index b86a173..6de25ff 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -19,6 +19,7 @@ from aeonlib.models import Angle, Window from aeonlib.salt.models import SaltSiderealTarget from aeonlib.salt.models.types import ( + Instrument, PositiveDuration, SalticamFilter, SkyTransparency, @@ -111,7 +112,7 @@ class Block(BaseModel): windows: list[Window] | None = None target: SaltSiderealTarget acquisition: Acquisition - instrument: None + instrument: Instrument pool: str | None = None data_notification: Literal["normal", "fast"] = "normal" diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py index d99d239..d100417 100644 --- a/src/aeonlib/salt/models/types/__init__.py +++ b/src/aeonlib/salt/models/types/__init__.py @@ -1,5 +1,5 @@ from .quantity import AstropyQuantityTypeAnnotation -from .block import SkyTransparency +from .block import Instrument, SkyTransparency from .duration import Duration, PositiveDuration from .filters import SalticamFilter from .target import MagnitudeBandpass, TargetType @@ -7,6 +7,7 @@ __all__ = [ "AstropyQuantityTypeAnnotation", "Duration", + "Instrument", "MagnitudeBandpass", "PositiveDuration", "SalticamFilter", diff --git a/src/aeonlib/salt/models/types/block.py b/src/aeonlib/salt/models/types/block.py index 6c7993b..247bea7 100644 --- a/src/aeonlib/salt/models/types/block.py +++ b/src/aeonlib/salt/models/types/block.py @@ -4,3 +4,5 @@ SkyTransparency = Literal["clear", "thin cloud", "thick cloud", "any"] """Sky transparency.""" + +Instrument = None From 6566ec10c86b11c330ef23bd4fbdbe6bf79fae9c Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 12:05:31 +0200 Subject: [PATCH 028/171] Refactor the model import --- src/aeonlib/salt/models/__init__.py | 12 ++++++++++-- tests/salt/models/conftest.py | 7 ++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 94a832c..e7470ba 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,6 +1,14 @@ from .target_models import MagnitudeRange, SaltSiderealTarget -from .block_models import Block, Constraints +from .block_models import Acquisition, Block, Constraints, ReferenceStar from .request_models import Request -__all__ = ["Block", "Constraints", "MagnitudeRange", "Request", "SaltSiderealTarget"] +__all__ = [ + "Acquisition", + "Block", + "Constraints", + "MagnitudeRange", + "ReferenceStar", + "Request", + "SaltSiderealTarget", +] diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index db09801..4b38aec 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -5,13 +5,14 @@ from astropy import units as u from aeonlib.salt.models import ( - Request, + Acquisition, Block, + Constraints, MagnitudeRange, + ReferenceStar, + Request, SaltSiderealTarget, - Constraints, ) -from aeonlib.salt.models.block_models import Acquisition, ReferenceStar @pytest.fixture() From a6907cda58dc80cf57d87fb76098bf328bdfa28a Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 12:25:18 +0200 Subject: [PATCH 029/171] Revert "Add an Instrument type" This reverts commit 5f83ed7b1f5a735289fa1e8d7eb72cff55f61dd6. --- src/aeonlib/salt/models/block_models.py | 3 +-- src/aeonlib/salt/models/types/__init__.py | 3 +-- src/aeonlib/salt/models/types/block.py | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 6de25ff..b86a173 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -19,7 +19,6 @@ from aeonlib.models import Angle, Window from aeonlib.salt.models import SaltSiderealTarget from aeonlib.salt.models.types import ( - Instrument, PositiveDuration, SalticamFilter, SkyTransparency, @@ -112,7 +111,7 @@ class Block(BaseModel): windows: list[Window] | None = None target: SaltSiderealTarget acquisition: Acquisition - instrument: Instrument + instrument: None pool: str | None = None data_notification: Literal["normal", "fast"] = "normal" diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py index d100417..d99d239 100644 --- a/src/aeonlib/salt/models/types/__init__.py +++ b/src/aeonlib/salt/models/types/__init__.py @@ -1,5 +1,5 @@ from .quantity import AstropyQuantityTypeAnnotation -from .block import Instrument, SkyTransparency +from .block import SkyTransparency from .duration import Duration, PositiveDuration from .filters import SalticamFilter from .target import MagnitudeBandpass, TargetType @@ -7,7 +7,6 @@ __all__ = [ "AstropyQuantityTypeAnnotation", "Duration", - "Instrument", "MagnitudeBandpass", "PositiveDuration", "SalticamFilter", diff --git a/src/aeonlib/salt/models/types/block.py b/src/aeonlib/salt/models/types/block.py index 247bea7..6c7993b 100644 --- a/src/aeonlib/salt/models/types/block.py +++ b/src/aeonlib/salt/models/types/block.py @@ -4,5 +4,3 @@ SkyTransparency = Literal["clear", "thin cloud", "thick cloud", "any"] """Sky transparency.""" - -Instrument = None From 858767760d370a1c9d5d980d19bf4bd0abdc54f0 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 12:40:56 +0200 Subject: [PATCH 030/171] Add the Salticam model --- src/aeonlib/salt/models/__init__.py | 2 + src/aeonlib/salt/models/salticam_models.py | 45 ++++++++++++++++++++++ tests/salt/models/conftest.py | 6 +++ tests/salt/models/test_salticam_models.py | 17 ++++++++ 4 files changed, 70 insertions(+) create mode 100644 src/aeonlib/salt/models/salticam_models.py create mode 100644 tests/salt/models/test_salticam_models.py diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index e7470ba..e9a371c 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,4 +1,5 @@ from .target_models import MagnitudeRange, SaltSiderealTarget +from .salticam_models import Salticam from .block_models import Acquisition, Block, Constraints, ReferenceStar from .request_models import Request @@ -10,5 +11,6 @@ "MagnitudeRange", "ReferenceStar", "Request", + "Salticam", "SaltSiderealTarget", ] diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py new file mode 100644 index 0000000..c9c3eba --- /dev/null +++ b/src/aeonlib/salt/models/salticam_models.py @@ -0,0 +1,45 @@ +from typing import Annotated + +from annotated_types import MinLen +from pydantic import BaseModel, PositiveInt + + +class Salticam(BaseModel): + """ + A Salticam instrument configuration. + + Several filters can be requested in the configuration. If you want to repeat the + sequence, you can set a number of cycles. This should not be confused with the + number of exposures, which is set for the detector. + + For example, if the configuration requests the Johnson U and Johnson V filter, one + cycle and two exposures correspond to the sequence + + U - U - V - V + + whereas two cycles and one exposure corresponds to + + U - V - U - V + + You may define a dither pattern, in which case the filter sequence (with its cycles + and exposures) applies to each dither pattern step. + + Attributes + ---------- + num_cycles + How often to cycle through the filter sequence. + filter_sequence + Filter sequence. + detector + Detector setup. + dither_pattern + Dither pattern. + include_flats: + Whether flats should be taken for the observation. + """ + + num_cycles: PositiveInt = 1 + filter_sequence: Annotated[list[None], MinLen(1)] + detector: None + dither_pattern: None = None + include_flats: bool diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 4b38aec..eebf547 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -11,6 +11,7 @@ MagnitudeRange, ReferenceStar, Request, + Salticam, SaltSiderealTarget, ) @@ -76,3 +77,8 @@ def base_acquisition(): @pytest.fixture() def base_reference_star(): return ReferenceStar(ra=117.564 * u.deg, dec=-63.9 * u.deg) + + +@pytest.fixture() +def base_salticam(): + return Salticam(filter_sequence=[None], detector=None, include_flats=True) diff --git a/tests/salt/models/test_salticam_models.py b/tests/salt/models/test_salticam_models.py new file mode 100644 index 0000000..6580a95 --- /dev/null +++ b/tests/salt/models/test_salticam_models.py @@ -0,0 +1,17 @@ +import pytest +from pydantic import ValidationError + +from aeonlib.salt.models import Salticam + + +class TestSalticam: + def test_salticam(self, base_salticam): + """Test that Salticam configurations can be built.""" + assert True + + def test_at_least_one_filter(self, base_salticam): + """Test that the filter sequence must have at least one step.""" + salticam = base_salticam.model_dump() + salticam["filter_sequence"] = [] + with pytest.raises(ValidationError, match="at least 1"): + Salticam(**salticam) From a1d07e0e0878fbcec3a2d2eb9bc92fc5026eac5d Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 16:55:39 +0200 Subject: [PATCH 031/171] Add the filter sequence step model --- src/aeonlib/salt/models/__init__.py | 3 ++- src/aeonlib/salt/models/salticam_models.py | 22 +++++++++++++++++++++- tests/salt/models/conftest.py | 12 ++++++++++-- tests/salt/models/test_salticam_models.py | 6 ++++++ 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index e9a371c..6b3dd17 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,5 +1,5 @@ from .target_models import MagnitudeRange, SaltSiderealTarget -from .salticam_models import Salticam +from .salticam_models import FilterSequenceStep, Salticam from .block_models import Acquisition, Block, Constraints, ReferenceStar from .request_models import Request @@ -8,6 +8,7 @@ "Acquisition", "Block", "Constraints", + "FilterSequenceStep", "MagnitudeRange", "ReferenceStar", "Request", diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index c9c3eba..8805212 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -1,8 +1,12 @@ +from __future__ import annotations + from typing import Annotated from annotated_types import MinLen from pydantic import BaseModel, PositiveInt +from aeonlib.salt.models.types import PositiveDuration, SalticamFilter + class Salticam(BaseModel): """ @@ -39,7 +43,23 @@ class Salticam(BaseModel): """ num_cycles: PositiveInt = 1 - filter_sequence: Annotated[list[None], MinLen(1)] + filter_sequence: Annotated[list[FilterSequenceStep], MinLen(1)] detector: None dither_pattern: None = None include_flats: bool + + +class FilterSequenceStep(BaseModel): + """ + A step in a filter sequence. + + Attributes + ---------- + filter + Filter for the step. + exposure_time + Exposure time for the step. + """ + + filter: SalticamFilter + exposure_time: PositiveDuration diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index eebf547..4634e6c 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -13,6 +13,7 @@ Request, Salticam, SaltSiderealTarget, + FilterSequenceStep, ) @@ -80,5 +81,12 @@ def base_reference_star(): @pytest.fixture() -def base_salticam(): - return Salticam(filter_sequence=[None], detector=None, include_flats=True) +def base_salticam(base_filter_sequence_step): + return Salticam( + filter_sequence=[base_filter_sequence_step], detector=None, include_flats=True + ) + + +@pytest.fixture() +def base_filter_sequence_step(): + return FilterSequenceStep(filter="Johnson B", exposure_time=409 * u.s) diff --git a/tests/salt/models/test_salticam_models.py b/tests/salt/models/test_salticam_models.py index 6580a95..5d3cf33 100644 --- a/tests/salt/models/test_salticam_models.py +++ b/tests/salt/models/test_salticam_models.py @@ -15,3 +15,9 @@ def test_at_least_one_filter(self, base_salticam): salticam["filter_sequence"] = [] with pytest.raises(ValidationError, match="at least 1"): Salticam(**salticam) + + +class TestFilterSequenceStep: + def test_filter_sequence_step(self, base_filter_sequence_step): + """Test that filter sequence steps can be built.""" + assert True From ade179f51996549657c104af081c4103f0026dae Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 17:34:55 +0200 Subject: [PATCH 032/171] Add the Salticam detector model --- src/aeonlib/salt/models/__init__.py | 3 ++- src/aeonlib/salt/models/salticam_models.py | 28 +++++++++++++++++++--- tests/salt/models/conftest.py | 18 ++++++++++++-- tests/salt/models/test_salticam_models.py | 7 ++++++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 6b3dd17..9951308 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,5 +1,5 @@ from .target_models import MagnitudeRange, SaltSiderealTarget -from .salticam_models import FilterSequenceStep, Salticam +from .salticam_models import FilterSequenceStep, Salticam, SalticamDetector from .block_models import Acquisition, Block, Constraints, ReferenceStar from .request_models import Request @@ -13,5 +13,6 @@ "ReferenceStar", "Request", "Salticam", + "SalticamDetector", "SaltSiderealTarget", ] diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index 8805212..8f8406d 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -1,11 +1,12 @@ from __future__ import annotations -from typing import Annotated +from typing import Annotated, Literal from annotated_types import MinLen from pydantic import BaseModel, PositiveInt from aeonlib.salt.models.types import PositiveDuration, SalticamFilter +from aeonlib.salt.validators import GreaterEqual, LessEqual class Salticam(BaseModel): @@ -44,7 +45,7 @@ class Salticam(BaseModel): num_cycles: PositiveInt = 1 filter_sequence: Annotated[list[FilterSequenceStep], MinLen(1)] - detector: None + detector: SalticamDetector dither_pattern: None = None include_flats: bool @@ -58,8 +59,29 @@ class FilterSequenceStep(BaseModel): filter Filter for the step. exposure_time - Exposure time for the step. + Exposure time for the step, as a `astropy.units.Quantity` or as a float in + seconds. """ filter: SalticamFilter exposure_time: PositiveDuration + + +class SalticamDetector(BaseModel): + """ + A Salticam detector setup. + + Only "normal" readout mode (i.e. a full frame readout) is supported. The readout + speed may be "fast" or "slow", the gain "bright" or "faint". Up to 9 CCD rows and + columns can be binned. + + The setup does not include the exposure time; this is set as part of a filter + sequence step. + """ + + num_exposures: PositiveInt + readout_mode: Literal["normal"] = "normal" + gain: Literal["bright", "faint"] + readout_speed: Literal["fast", "slow"] + num_prebinned_rows: Annotated[int, GreaterEqual(1), LessEqual(9)] + num_prebinned_columns: Annotated[int, GreaterEqual(1), LessEqual(9)] diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 4634e6c..c435d34 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -14,6 +14,7 @@ Salticam, SaltSiderealTarget, FilterSequenceStep, + SalticamDetector, ) @@ -81,12 +82,25 @@ def base_reference_star(): @pytest.fixture() -def base_salticam(base_filter_sequence_step): +def base_salticam(base_salticam_detector, base_filter_sequence_step): return Salticam( - filter_sequence=[base_filter_sequence_step], detector=None, include_flats=True + filter_sequence=[base_filter_sequence_step], + detector=base_salticam_detector, + include_flats=True, ) @pytest.fixture() def base_filter_sequence_step(): return FilterSequenceStep(filter="Johnson B", exposure_time=409 * u.s) + + +@pytest.fixture() +def base_salticam_detector(): + return SalticamDetector( + num_exposures=1, + gain="bright", + readout_speed="fast", + num_prebinned_rows=2, + num_prebinned_columns=2, + ) diff --git a/tests/salt/models/test_salticam_models.py b/tests/salt/models/test_salticam_models.py index 5d3cf33..15bc40a 100644 --- a/tests/salt/models/test_salticam_models.py +++ b/tests/salt/models/test_salticam_models.py @@ -2,6 +2,7 @@ from pydantic import ValidationError from aeonlib.salt.models import Salticam +from tests.salt.models.conftest import base_salticam_detector class TestSalticam: @@ -21,3 +22,9 @@ class TestFilterSequenceStep: def test_filter_sequence_step(self, base_filter_sequence_step): """Test that filter sequence steps can be built.""" assert True + + +class TestSalticamDetector: + def test_salticam_detector(self, base_salticam_detector): + """Test that Salticam detector setups can be built.""" + assert True From 644deb010e91383e4ea193de13a393e9764c452b Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 17:36:56 +0200 Subject: [PATCH 033/171] Rename FilterSequenceStep to SalticamFilterSequenceStep --- src/aeonlib/salt/models/__init__.py | 4 ++-- src/aeonlib/salt/models/salticam_models.py | 4 ++-- tests/salt/models/conftest.py | 10 +++++----- tests/salt/models/test_salticam_models.py | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 9951308..a47ed65 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,5 +1,5 @@ from .target_models import MagnitudeRange, SaltSiderealTarget -from .salticam_models import FilterSequenceStep, Salticam, SalticamDetector +from .salticam_models import SalticamFilterSequenceStep, Salticam, SalticamDetector from .block_models import Acquisition, Block, Constraints, ReferenceStar from .request_models import Request @@ -8,7 +8,7 @@ "Acquisition", "Block", "Constraints", - "FilterSequenceStep", + "SalticamFilterSequenceStep", "MagnitudeRange", "ReferenceStar", "Request", diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index 8f8406d..bf36714 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -44,13 +44,13 @@ class Salticam(BaseModel): """ num_cycles: PositiveInt = 1 - filter_sequence: Annotated[list[FilterSequenceStep], MinLen(1)] + filter_sequence: Annotated[list[SalticamFilterSequenceStep], MinLen(1)] detector: SalticamDetector dither_pattern: None = None include_flats: bool -class FilterSequenceStep(BaseModel): +class SalticamFilterSequenceStep(BaseModel): """ A step in a filter sequence. diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index c435d34..a868c1b 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -13,7 +13,7 @@ Request, Salticam, SaltSiderealTarget, - FilterSequenceStep, + SalticamFilterSequenceStep, SalticamDetector, ) @@ -82,17 +82,17 @@ def base_reference_star(): @pytest.fixture() -def base_salticam(base_salticam_detector, base_filter_sequence_step): +def base_salticam(base_salticam_detector, base_salticam_filter_sequence_step): return Salticam( - filter_sequence=[base_filter_sequence_step], + filter_sequence=[base_salticam_filter_sequence_step], detector=base_salticam_detector, include_flats=True, ) @pytest.fixture() -def base_filter_sequence_step(): - return FilterSequenceStep(filter="Johnson B", exposure_time=409 * u.s) +def base_salticam_filter_sequence_step(): + return SalticamFilterSequenceStep(filter="Johnson B", exposure_time=409 * u.s) @pytest.fixture() diff --git a/tests/salt/models/test_salticam_models.py b/tests/salt/models/test_salticam_models.py index 15bc40a..b690ef7 100644 --- a/tests/salt/models/test_salticam_models.py +++ b/tests/salt/models/test_salticam_models.py @@ -18,8 +18,8 @@ def test_at_least_one_filter(self, base_salticam): Salticam(**salticam) -class TestFilterSequenceStep: - def test_filter_sequence_step(self, base_filter_sequence_step): +class TestSalticamFilterSequenceStep: + def test_salticam_filter_sequence_step(self, base_salticam_filter_sequence_step): """Test that filter sequence steps can be built.""" assert True From 14d651adadb9632f08ea1d6f174553a9beb6a0c6 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 17 Sep 2025 17:38:11 +0200 Subject: [PATCH 034/171] Remove spurious import --- tests/salt/models/test_salticam_models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/salt/models/test_salticam_models.py b/tests/salt/models/test_salticam_models.py index b690ef7..dfd67b2 100644 --- a/tests/salt/models/test_salticam_models.py +++ b/tests/salt/models/test_salticam_models.py @@ -2,7 +2,6 @@ from pydantic import ValidationError from aeonlib.salt.models import Salticam -from tests.salt.models.conftest import base_salticam_detector class TestSalticam: From 9dc6a37488db6450ed0eeed26b8ce4f214c2b10f Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 19 Sep 2025 14:29:47 +0200 Subject: [PATCH 035/171] Add the Salticam dither pattern model --- src/aeonlib/salt/models/__init__.py | 8 ++- src/aeonlib/salt/models/salticam_models.py | 59 ++++++++++++++++++++-- tests/salt/models/conftest.py | 6 +++ tests/salt/models/test_salticam_models.py | 46 ++++++++++++++++- 4 files changed, 113 insertions(+), 6 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index a47ed65..9d4f044 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,5 +1,10 @@ from .target_models import MagnitudeRange, SaltSiderealTarget -from .salticam_models import SalticamFilterSequenceStep, Salticam, SalticamDetector +from .salticam_models import ( + SalticamFilterSequenceStep, + Salticam, + SalticamDetector, + SalticamDitherPattern, +) from .block_models import Acquisition, Block, Constraints, ReferenceStar from .request_models import Request @@ -8,6 +13,7 @@ "Acquisition", "Block", "Constraints", + "SalticamDitherPattern", "SalticamFilterSequenceStep", "MagnitudeRange", "ReferenceStar", diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index bf36714..6b8533c 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -1,11 +1,17 @@ from __future__ import annotations -from typing import Annotated, Literal +from typing import Annotated, Literal, Union from annotated_types import MinLen -from pydantic import BaseModel, PositiveInt - -from aeonlib.salt.models.types import PositiveDuration, SalticamFilter +from astropy import units as u +from astropy.units import Quantity +from pydantic import BaseModel, PositiveInt, Field, model_validator + +from aeonlib.salt.models.types import ( + PositiveDuration, + SalticamFilter, + AstropyQuantityTypeAnnotation, +) from aeonlib.salt.validators import GreaterEqual, LessEqual @@ -85,3 +91,48 @@ class SalticamDetector(BaseModel): readout_speed: Literal["fast", "slow"] num_prebinned_rows: Annotated[int, GreaterEqual(1), LessEqual(9)] num_prebinned_columns: Annotated[int, GreaterEqual(1), LessEqual(9)] + + +class SalticamDitherPattern(BaseModel): + """ + A dither pattern for Salticam. + + The dither pattern is characterised by the number of rows and columns it covers, + the number of steps to take, and the offset between the steps. + + By default, the number of steps is the product of rows and columns, but you may + specify a multiple of that number if you want to perform the pattern more than once. + + The offset is in detector coordinates, not in right ascension and declination. + Therefore, if a particular object orientation is desired, a suitable position + angle must be chosen so that the dithers coincide with the detector axes. + + Attributes + ---------- + num_rows + Number of rows in the pattern. + num_columns + Number of columns in the pattern. + number_steps + Number of steps to perform. + offset + Offset between steps, as a `astropy.units.Quantity` or as a float in arcsec. + """ + + num_rows: PositiveInt + num_columns: PositiveInt + num_steps: PositiveInt = Field( + default_factory=lambda data: data["num_rows"] * data["num_columns"] + ) + offset: Annotated[ + Union[Quantity, float], AstropyQuantityTypeAnnotation(default_unit=u.arcsec) + ] + + @model_validator(mode="after") + def check_number_of_steps(self): + if self.num_steps % (self.num_rows * self.num_columns) != 0: + raise ValueError( + "The number of steps must be the number of rows times the number of " + "columns, or a multiple thereof." + ) + return self diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index a868c1b..93f1c99 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -12,6 +12,7 @@ ReferenceStar, Request, Salticam, + SalticamDitherPattern, SaltSiderealTarget, SalticamFilterSequenceStep, SalticamDetector, @@ -104,3 +105,8 @@ def base_salticam_detector(): num_prebinned_rows=2, num_prebinned_columns=2, ) + + +@pytest.fixture() +def base_salticam_dither_pattern(): + return SalticamDitherPattern(num_rows=3, num_columns=4, offset=12.9) diff --git a/tests/salt/models/test_salticam_models.py b/tests/salt/models/test_salticam_models.py index dfd67b2..f6875d1 100644 --- a/tests/salt/models/test_salticam_models.py +++ b/tests/salt/models/test_salticam_models.py @@ -1,7 +1,9 @@ +from contextlib import nullcontext + import pytest from pydantic import ValidationError -from aeonlib.salt.models import Salticam +from aeonlib.salt.models import Salticam, SalticamDitherPattern class TestSalticam: @@ -27,3 +29,45 @@ class TestSalticamDetector: def test_salticam_detector(self, base_salticam_detector): """Test that Salticam detector setups can be built.""" assert True + + +class TestSalticamDitherPattern: + def test_salticam_dither_pattern(self, base_salticam_dither_pattern): + """Test that Salticam dither pattern can be built.""" + assert True + + def test_default_number_of_steps(self, base_salticam_dither_pattern): + dither_pattern = base_salticam_dither_pattern.model_dump() + dither_pattern["num_rows"] = 4 + dither_pattern["num_columns"] = 3 + if "num_steps" in dither_pattern: + del dither_pattern["num_steps"] + assert SalticamDitherPattern(**dither_pattern).num_steps == 12 + + @pytest.mark.parametrize( + "num_rows, num_columns, num_steps, expectation", + [ + (1, 1, 1, nullcontext()), + (1, 1, 5, nullcontext()), + (1, 2, 2, nullcontext()), + (2, 1, 6, nullcontext()), + (3, 5, 15, nullcontext()), + (5, 3, 45, nullcontext()), + (5, 2, 9, pytest.raises(ValidationError)), + (3, 7, 43, pytest.raises(ValidationError)), + ], + ) + def test_only_complete_patterns_allowed( + self, + num_rows, + num_columns, + num_steps, + expectation, + base_salticam_dither_pattern, + ): + dither_pattern = base_salticam_dither_pattern.model_dump() + dither_pattern["num_rows"] = num_rows + dither_pattern["num_columns"] = num_columns + dither_pattern["num_steps"] = num_steps + with expectation: + SalticamDitherPattern(**dither_pattern) From c13c777c9f5e6dcf39ffe515e872a9dafbf790a3 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 19 Sep 2025 16:35:07 +0200 Subject: [PATCH 036/171] Add the dither pattern to the Salticam model --- src/aeonlib/salt/models/salticam_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index 6b8533c..6e4342f 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -52,7 +52,7 @@ class Salticam(BaseModel): num_cycles: PositiveInt = 1 filter_sequence: Annotated[list[SalticamFilterSequenceStep], MinLen(1)] detector: SalticamDetector - dither_pattern: None = None + dither_pattern: SalticamDitherPattern = None include_flats: bool From aba55ff44b5aeb6d3db074ac67d3e0aeaea98d0a Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 19 Sep 2025 16:35:37 +0200 Subject: [PATCH 037/171] Avoid misleading type checker errors --- tests/salt/models/test_block_models.py | 4 ++-- tests/salt/models/test_salticam_models.py | 6 +++--- tests/salt/models/test_target_models.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/salt/models/test_block_models.py b/tests/salt/models/test_block_models.py index f1a2f10..99b90c3 100644 --- a/tests/salt/models/test_block_models.py +++ b/tests/salt/models/test_block_models.py @@ -34,7 +34,7 @@ def test_max_visits_and_visits( block["max_num_visits"] = max_num_visits with expectation: - Block(**block) + Block(**block) # type: ignore assert True @@ -69,4 +69,4 @@ def test_dec_range(self, dec, expectation, base_reference_star): ref_star = base_reference_star.model_dump() ref_star["dec"] = dec with expectation: - ReferenceStar(**ref_star) + ReferenceStar(**ref_star) # type: ignore diff --git a/tests/salt/models/test_salticam_models.py b/tests/salt/models/test_salticam_models.py index f6875d1..3fdbd92 100644 --- a/tests/salt/models/test_salticam_models.py +++ b/tests/salt/models/test_salticam_models.py @@ -16,7 +16,7 @@ def test_at_least_one_filter(self, base_salticam): salticam = base_salticam.model_dump() salticam["filter_sequence"] = [] with pytest.raises(ValidationError, match="at least 1"): - Salticam(**salticam) + Salticam(**salticam) # type: ignore class TestSalticamFilterSequenceStep: @@ -42,7 +42,7 @@ def test_default_number_of_steps(self, base_salticam_dither_pattern): dither_pattern["num_columns"] = 3 if "num_steps" in dither_pattern: del dither_pattern["num_steps"] - assert SalticamDitherPattern(**dither_pattern).num_steps == 12 + assert SalticamDitherPattern(**dither_pattern).num_steps == 12 # type: ignore @pytest.mark.parametrize( "num_rows, num_columns, num_steps, expectation", @@ -70,4 +70,4 @@ def test_only_complete_patterns_allowed( dither_pattern["num_columns"] = num_columns dither_pattern["num_steps"] = num_steps with expectation: - SalticamDitherPattern(**dither_pattern) + SalticamDitherPattern(**dither_pattern) # type: ignore diff --git a/tests/salt/models/test_target_models.py b/tests/salt/models/test_target_models.py index dbc9119..9049258 100644 --- a/tests/salt/models/test_target_models.py +++ b/tests/salt/models/test_target_models.py @@ -26,7 +26,7 @@ def test_dec_range(self, dec, expectation, base_target): target = base_target.model_dump() target["dec"] = dec with expectation: - SaltSiderealTarget(**target) + SaltSiderealTarget(**target) # type: ignore class TestMagnitudeRange: @@ -53,6 +53,6 @@ def test_min_and_max_magnitude( magnitude_range["max_magnitude"] = max_magnitude with expectation: - MagnitudeRange(**magnitude_range) + MagnitudeRange(**magnitude_range) # type: ignore assert True From befed114581ba7b77a466b660acd9d5fe2b4bb4d Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 22 Oct 2025 09:40:08 +0200 Subject: [PATCH 038/171] Add a model for RSS configurations --- src/aeonlib/salt/models/__init__.py | 2 ++ src/aeonlib/salt/models/rss_models.py | 51 +++++++++++++++++++++++++++ tests/salt/models/conftest.py | 6 ++++ tests/salt/models/test_rss_models.py | 4 +++ 4 files changed, 63 insertions(+) create mode 100644 src/aeonlib/salt/models/rss_models.py create mode 100644 tests/salt/models/test_rss_models.py diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 9d4f044..4158388 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,4 +1,5 @@ from .target_models import MagnitudeRange, SaltSiderealTarget +from .rss_models import Rss from .salticam_models import ( SalticamFilterSequenceStep, Salticam, @@ -18,6 +19,7 @@ "MagnitudeRange", "ReferenceStar", "Request", + "Rss", "Salticam", "SalticamDetector", "SaltSiderealTarget", diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py new file mode 100644 index 0000000..e67c0b0 --- /dev/null +++ b/src/aeonlib/salt/models/rss_models.py @@ -0,0 +1,51 @@ +from pydantic import BaseModel, PositiveInt + + +class Rss(BaseModel): + """ + An RSS configuration. + + RSS can be used in different configurations: + + - Imaging. + - Longslit spectroscopy. + - Multiobject spectroscopy (MOS). + - Spectroscopy with a Slit Mask Integrated Fibre Unit (IFU). + + All of these may be used for polarimetric observations, i.e. with a wave plate + pattern for a half wave plate H and a quarter wave plate Q. If you want to + perform this pattern more than once, you can specify a number of cycles. These + should not be confused with the number of exposures, which is defined for the + detector. + + For example, assume you perform polarimetry with a pattern (H1, Q1), (H2, Q2). + Then two cycles and 1 exposure would result in the following observing + sequence: + + (H1, Q1) - (H2, Q2) - (H1, Q1) - (H2, Q2) + + On the other hand, one cycle and two exposures would result in the following + sequence: + + (H1, Q1) - (H1, Q1) - (H2, Q2) - (H2, Q2) + + You may define a dither pattern, in which case the wave plate sequence (with its + cycles and exposures) applies to each dither pattern step. + + Attributes + ---------- + num_cycles + How often to cycle through the wave plate sequence. This is only relevant if + you perform polarimetry. + configuration + Imaging or spectroscopy configuration. + detector + Detector setup. + dither_pattern + Dither pattern. + """ + + num_cycles: PositiveInt = 1 + configuration: None + detector: None + dither_pattern: None diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 93f1c99..c5396f0 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -16,6 +16,7 @@ SaltSiderealTarget, SalticamFilterSequenceStep, SalticamDetector, + Rss, ) @@ -110,3 +111,8 @@ def base_salticam_detector(): @pytest.fixture() def base_salticam_dither_pattern(): return SalticamDitherPattern(num_rows=3, num_columns=4, offset=12.9) + + +@pytest.fixture() +def base_rss(): + return Rss(configuration=None, detector=None, dither_pattern=None) diff --git a/tests/salt/models/test_rss_models.py b/tests/salt/models/test_rss_models.py new file mode 100644 index 0000000..79a2677 --- /dev/null +++ b/tests/salt/models/test_rss_models.py @@ -0,0 +1,4 @@ +class TestRss: + def test_rss(self, base_rss): + """Test that RSS configurations can be built.""" + assert True From edf30fb75c57f3b2ed1118ef7de3e650499e56d3 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 22 Oct 2025 09:40:23 +0200 Subject: [PATCH 039/171] Add RSS imaging filters --- src/aeonlib/salt/models/types/filters.py | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/aeonlib/salt/models/types/filters.py b/src/aeonlib/salt/models/types/filters.py index db5eaee..ddb299d 100644 --- a/src/aeonlib/salt/models/types/filters.py +++ b/src/aeonlib/salt/models/types/filters.py @@ -26,3 +26,48 @@ "SRE 4", ] """A filter for the Salticam detector.""" + + +RssImagingFilter = Literal[ + "pi04340", + "pi04400", + "pi04465", + "pi04530", + "pi04600", + "pi04670", + "pi04740", + "pi04820", + "pi04895", + "pi04975", + "pi05060", + "pi05145", + "pi05235", + "pi05325", + "pi05420", + "pi05520", + "pi05620", + "pi05725", + "pi05830", + "pi05945", + "pi06055", + "pi06170", + "pi06290", + "pi06410", + "pi06530", + "pi06645", + "pi06765", + "pi06885", + "pi07005", + "pi07130", + "pi07260", + "pi07390", + "pi07535", + "pi07685", + "pi07840", + "pi08005", + "pi08175", + "pi08350", + "pi08535", + "pi08730", +] +"""An imaging filter for the RSS detector.""" From efb556c70cb0fb078c8f25a3911cb069533b25d0 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 29 Oct 2025 19:47:08 +0200 Subject: [PATCH 040/171] Add models for RSS imaging and configurations and RSS polarimetry setups --- src/aeonlib/salt/models/__init__.py | 4 +- src/aeonlib/salt/models/rss_models.py | 86 ++++++++++++++++++++++++++- tests/salt/models/conftest.py | 16 ++++- tests/salt/models/test_rss_models.py | 36 +++++++++++ 4 files changed, 137 insertions(+), 5 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 4158388..4af2119 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,5 +1,5 @@ from .target_models import MagnitudeRange, SaltSiderealTarget -from .rss_models import Rss +from .rss_models import Rss, RssImaging, RssPolarimetry from .salticam_models import ( SalticamFilterSequenceStep, Salticam, @@ -20,6 +20,8 @@ "ReferenceStar", "Request", "Rss", + "RssImaging", + "RssPolarimetry", "Salticam", "SalticamDetector", "SaltSiderealTarget", diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index e67c0b0..e800022 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -1,4 +1,11 @@ -from pydantic import BaseModel, PositiveInt +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, PositiveInt, field_validator + +from aeonlib.types import Angle +from aeonlib.salt.models.types.filters import RssImagingFilter, SalticamFilter class Rss(BaseModel): @@ -46,6 +53,81 @@ class Rss(BaseModel): """ num_cycles: PositiveInt = 1 - configuration: None + configuration: RssImaging detector: None dither_pattern: None + + +class RssImaging(BaseModel): + """ + An RSS imaging configuration. + + Attributes + ---------- + filter + The filter to use. This may be one of RSS's own imaging filters or one of the + filters used by Salticam. + polarimetry + The (optional) polarimetry setup to use. + include_flat + Whether a nighttime flat should be taken for the observation. + """ + + filter: RssImagingFilter | SalticamFilter + polarimetry: RssPolarimetry | None + include_flat: bool + + +_WavePlatePattern = ( + Literal["linear", "linear hi", "circular", "all-Stokes"] + | list[tuple[Angle | None, Angle | None]] +) + + +class RssPolarimetry(BaseModel): + """ + An RSS polarimetry setup. + + The setup is defined by a wave plate pattern, which may be specified by the name of + a predefined pattern ("linear", "linear hi", "circular" or "all-Stokes") or by + explicitly defining the list of half and quarter wave plate angles. In case of the + latter, the list must consist of pairs of angles, with the first angle being that of + the half wave plate and the second being that of the quarter wave plate. The angle + may be None if the respectjve wave plate is not used. + + For example, the "linear" and "circular" patterns could be given as:: + + linear = RssPolarimetry(wave_plate_pattern="linear") + circular = RssPolarimetry(wave_plate_pattern="linear") + + or as:: + + from astropy import units as u + + linear = RssPolarimetry( + wave_plate_pattern=[ + (0 * u.deg, None), + (45 * u.deg, None), + (22.5 * u.deg, None), + (67.5 * u.deg, None), + ] + ) + circular = RssPolarimetry( + wave_plate_pattern=[(0 * u.deg, 45 * u.deg), (0 * u.deg, 315 * u.deg)] + ) + + A wave plate pattern may have up to 8 steps. + """ + + wave_plate_pattern: _WavePlatePattern + + @field_validator("wave_plate_pattern", mode="after") + @classmethod + def check_pattern_size(cls, value: _WavePlatePattern) -> _WavePlatePattern: + if isinstance(value, str): + return value + + if len(value) < 1 or len(value) > 8: + raise ValueError("The wave plate pattern must have between 1 and 8 steps.") + + return value diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index c5396f0..3618980 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -17,7 +17,9 @@ SalticamFilterSequenceStep, SalticamDetector, Rss, + RssImaging, ) +from aeonlib.salt.models.rss_models import RssPolarimetry @pytest.fixture() @@ -114,5 +116,15 @@ def base_salticam_dither_pattern(): @pytest.fixture() -def base_rss(): - return Rss(configuration=None, detector=None, dither_pattern=None) +def base_rss(base_rss_imaging): + return Rss(configuration=base_rss_imaging, detector=None, dither_pattern=None) + + +@pytest.fixture() +def base_rss_polarimetry(): + return RssPolarimetry(wave_plate_pattern="linear") + + +@pytest.fixture() +def base_rss_imaging(): + return RssImaging(filter="pi04400", polarimetry=None, include_flat=True) diff --git a/tests/salt/models/test_rss_models.py b/tests/salt/models/test_rss_models.py index 79a2677..ee8a41b 100644 --- a/tests/salt/models/test_rss_models.py +++ b/tests/salt/models/test_rss_models.py @@ -1,4 +1,40 @@ +from contextlib import nullcontext + +import pytest +from astropy import units as u + +from aeonlib.salt.models import RssPolarimetry + + class TestRss: def test_rss(self, base_rss): """Test that RSS configurations can be built.""" assert True + + +class TestRssImaging: + def test_rss_imaging(self, base_rss_imaging): + """Test that RSS imaging configurations can be built.""" + assert True + + +class TestRssPolarimetry: + def test_rss_polarimetry(self, base_rss_polarimetry): + """Test that RSS polarimetry setups can be built.""" + assert True + + @pytest.mark.parametrize( + "pattern, expectation", + [ + ("linear", nullcontext()), + ("all-Stokes", nullcontext()), + ([], pytest.raises(ValueError)), + ([(10 * u.deg, 20 * u.deg)], nullcontext()), + ([(10 * u.deg, 20 * u.deg)] * 8, nullcontext()), + ([(10 * u.deg, 20 * u.deg)] * 9, pytest.raises(ValueError)), + ], + ) + def test_pattern_must_have_between_1_and_8_steps(self, pattern, expectation): + """Test that the wave pattern must have between 1 and 8 steps.""" + with expectation: + RssPolarimetry(wave_plate_pattern=pattern) From eff32810eade618a498b47e25d054744d4c31ae5 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 30 Oct 2025 10:09:38 +0200 Subject: [PATCH 041/171] Check that the angles in a wave plate pattern are correct --- src/aeonlib/salt/models/rss_models.py | 41 +++++++++++++++++++++++---- tests/salt/models/test_rss_models.py | 29 +++++++++++++++++-- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index e800022..6dcfec5 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -2,6 +2,7 @@ from typing import Literal +from astropy import units as u from pydantic import BaseModel, PositiveInt, field_validator from aeonlib.types import Angle @@ -88,12 +89,13 @@ class RssPolarimetry(BaseModel): """ An RSS polarimetry setup. - The setup is defined by a wave plate pattern, which may be specified by the name of - a predefined pattern ("linear", "linear hi", "circular" or "all-Stokes") or by - explicitly defining the list of half and quarter wave plate angles. In case of the - latter, the list must consist of pairs of angles, with the first angle being that of - the half wave plate and the second being that of the quarter wave plate. The angle - may be None if the respectjve wave plate is not used. + The setup is defined by a wave plate pattern, which may be specified by the name + of a predefined pattern ("linear", "linear hi", "circular" or "all-Stokes") or by + explicitly defining the list of half and quarter wave plate angles. In case of + the latter, the list must consist of pairs of angles, with the first angle being + that of the half wave plate and the second being that of the quarter wave plate. + Each angle must bee a multiple of 11.25 degrees. It may be None if the respective + wave plate is not used. For example, the "linear" and "circular" patterns could be given as:: @@ -131,3 +133,30 @@ def check_pattern_size(cls, value: _WavePlatePattern) -> _WavePlatePattern: raise ValueError("The wave plate pattern must have between 1 and 8 steps.") return value + + @classmethod + def _check_pattern_step(cls, step: tuple[Angle, Angle]) -> None: + error = ( + "Each angle in a wave plate pattern must be a multiple of 11.25 degrees " + "between 0 degrees (inclusive) and 360 degrees (exclusive)." + ) + for angle in step: + if angle < 0 * u.deg or angle >= 360 * u.deg: + raise ValueError(error) + + # Check that the ratio of the angle and 11.25 deg is (very close to) an + # integer. + x = (angle.to(u.deg) / 11.25).value + if abs(round(x) - x) > 1e-6: + raise ValueError(error) + + @field_validator("wave_plate_pattern", mode="after") + @classmethod + def check_angle_values(cls, value: _WavePlatePattern) -> _WavePlatePattern: + if isinstance(value, str): + return value + + for step in value: + RssPolarimetry._check_pattern_step(step) + + return value diff --git a/tests/salt/models/test_rss_models.py b/tests/salt/models/test_rss_models.py index ee8a41b..9639e61 100644 --- a/tests/salt/models/test_rss_models.py +++ b/tests/salt/models/test_rss_models.py @@ -1,3 +1,4 @@ +import math from contextlib import nullcontext import pytest @@ -29,12 +30,34 @@ def test_rss_polarimetry(self, base_rss_polarimetry): ("linear", nullcontext()), ("all-Stokes", nullcontext()), ([], pytest.raises(ValueError)), - ([(10 * u.deg, 20 * u.deg)], nullcontext()), - ([(10 * u.deg, 20 * u.deg)] * 8, nullcontext()), - ([(10 * u.deg, 20 * u.deg)] * 9, pytest.raises(ValueError)), + ([(45 * u.deg, 90 * u.deg)], nullcontext()), + ([(45 * u.deg, 90 * u.deg)] * 8, nullcontext()), + ([(45 * u.deg, 90 * u.deg)] * 9, pytest.raises(ValueError)), ], ) def test_pattern_must_have_between_1_and_8_steps(self, pattern, expectation): """Test that the wave pattern must have between 1 and 8 steps.""" with expectation: RssPolarimetry(wave_plate_pattern=pattern) + + @pytest.mark.parametrize( + "angle, expectation", + [ + (0, nullcontext()), + (11.25, nullcontext()), + ((math.pi / 4) * u.rad, nullcontext()), # 45 degrees in radians + (303.75, nullcontext()), + (-0.01, pytest.raises(ValueError)), + (-11.25, pytest.raises(ValueError)), + (11.24, pytest.raises(ValueError)), + (303.76, pytest.raises(ValueError)), + (360, pytest.raises(ValueError)), + (405, pytest.raises(ValueError)), + ], + ) + def test_angles_must_have_allowed_value(self, angle, expectation): + # Test that wave plater pattern angles must be a multiple of 11.25 deg between + # 0 deg (inclusive) and 360 deg (exclusive). + with expectation: + RssPolarimetry(wave_plate_pattern=[(angle, 45 * u.deg)]) + RssPolarimetry(wave_plate_pattern=[(45 * u.deg, angle)]) From afd604686056df419b8c6c518b18f627ea403ca7 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 30 Oct 2025 10:11:13 +0200 Subject: [PATCH 042/171] Change include_flats to include_flat for Salticam --- src/aeonlib/salt/models/salticam_models.py | 6 +++--- tests/salt/models/conftest.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index 6e4342f..37ba71c 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -45,15 +45,15 @@ class Salticam(BaseModel): Detector setup. dither_pattern Dither pattern. - include_flats: - Whether flats should be taken for the observation. + include_flat: + Whether a nighttime flat should be taken for the observation. """ num_cycles: PositiveInt = 1 filter_sequence: Annotated[list[SalticamFilterSequenceStep], MinLen(1)] detector: SalticamDetector dither_pattern: SalticamDitherPattern = None - include_flats: bool + include_flat: bool class SalticamFilterSequenceStep(BaseModel): diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 3618980..209f474 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -90,7 +90,7 @@ def base_salticam(base_salticam_detector, base_salticam_filter_sequence_step): return Salticam( filter_sequence=[base_salticam_filter_sequence_step], detector=base_salticam_detector, - include_flats=True, + include_flat=True, ) From 3f02ffe6e9345db400c70b9fc008e349c099eb5a Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 30 Oct 2025 20:56:01 +0200 Subject: [PATCH 043/171] Update comments and add RSS order blocking filters --- src/aeonlib/salt/models/types/filters.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/aeonlib/salt/models/types/filters.py b/src/aeonlib/salt/models/types/filters.py index ddb299d..6465ff1 100644 --- a/src/aeonlib/salt/models/types/filters.py +++ b/src/aeonlib/salt/models/types/filters.py @@ -25,7 +25,7 @@ "SRE 3", "SRE 4", ] -"""A filter for the Salticam detector.""" +"""A filter for Salticam.""" RssImagingFilter = Literal[ @@ -70,4 +70,8 @@ "pi08535", "pi08730", ] -"""An imaging filter for the RSS detector.""" +"""An imaging filter for RSS.""" + + +RssOrderBlockingFilter = Literal["pc00000", "pc03200", "pc03400", "pc03850", "pc04600"] +"""An order blocking filter for RSS.""" From 1792e2cf8af96b6df1fbf780907ebc43f7c176d9 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 30 Oct 2025 20:59:38 +0200 Subject: [PATCH 044/171] Add the RSS spectroscopy base model --- src/aeonlib/salt/models/rss_models.py | 51 ++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 6dcfec5..8d6a8ef 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -6,7 +6,11 @@ from pydantic import BaseModel, PositiveInt, field_validator from aeonlib.types import Angle -from aeonlib.salt.models.types.filters import RssImagingFilter, SalticamFilter +from aeonlib.salt.models.types.filters import ( + RssImagingFilter, + RssOrderBlockingFilter, + SalticamFilter, +) class Rss(BaseModel): @@ -46,7 +50,7 @@ class Rss(BaseModel): How often to cycle through the wave plate sequence. This is only relevant if you perform polarimetry. configuration - Imaging or spectroscopy configuration. + Imaging, longslit, multiobject spectroscopy or slit mask IFU configuration. detector Detector setup. dither_pattern @@ -69,13 +73,13 @@ class RssImaging(BaseModel): The filter to use. This may be one of RSS's own imaging filters or one of the filters used by Salticam. polarimetry - The (optional) polarimetry setup to use. + The (optional) polarimetry setup. include_flat Whether a nighttime flat should be taken for the observation. """ filter: RssImagingFilter | SalticamFilter - polarimetry: RssPolarimetry | None + polarimetry: RssPolarimetry | None = None include_flat: bool @@ -85,6 +89,45 @@ class RssImaging(BaseModel): ) +class RssSpectroscopy(BaseModel): + """ + An RSS spectroscopy configuration. + + While the grating, articulation, polarimetry and calibrations are defined by this + class, the slit mask (or IFU) to use is specified in a child class. + + Attributes + ---------- + grating + The barcode of the grating, such as "pg0900". + grating_angle + The grating angle. The default is half the articulation angle. + articulation_angle + The articulation angle of the camera. This must be either 0 deg or one of the + values 1.75 deg + (n - 1) * 0.75 deg, where 1 <= n <= 132. + order_blocking_filter + The order blocking filter. + polarimetry + The (optional) polarimetry setup. + include_flat + Whether a nighttime flat should be taken for the observation. + include_arc + Whether a nighttime arc should be taken for the observation. + request_photometric_standard + Whether a photometric standard should be taken for the observation. + + """ + + grating: Literal["pg0700", "pg0900", "pg1300", "pg1800", "pg2300", "pg3000"] + grating_angle: Angle + articulation_angle: Angle + order_blocking_filter: RssOrderBlockingFilter + polarimetry: RssPolarimetry | None = None + include_flat: bool + include_arc: bool = True + request_spectrophotometric_standard: bool = False + + class RssPolarimetry(BaseModel): """ An RSS polarimetry setup. From d39f3b550f4c4f4bae6bc3f5050080b9c38d8195 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 9 Nov 2025 14:23:19 +0200 Subject: [PATCH 045/171] Add validation for grating and articulation angles --- src/aeonlib/salt/models/__init__.py | 3 ++- src/aeonlib/salt/models/rss_models.py | 27 +++++++++++++++++++-- tests/salt/models/conftest.py | 17 ++++++++++++- tests/salt/models/test_rss_models.py | 35 +++++++++++++++++++++++++-- 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 4af2119..38c0330 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,5 +1,5 @@ from .target_models import MagnitudeRange, SaltSiderealTarget -from .rss_models import Rss, RssImaging, RssPolarimetry +from .rss_models import Rss, RssImaging, RssPolarimetry, RssSpectroscopy from .salticam_models import ( SalticamFilterSequenceStep, Salticam, @@ -22,6 +22,7 @@ "Rss", "RssImaging", "RssPolarimetry", + "RssSpectroscopy", "Salticam", "SalticamDetector", "SaltSiderealTarget", diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 8d6a8ef..5ade117 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Literal +from typing import Literal, Annotated from astropy import units as u from pydantic import BaseModel, PositiveInt, field_validator @@ -11,6 +11,7 @@ RssOrderBlockingFilter, SalticamFilter, ) +from aeonlib.salt.validators import GreaterEqual, LessEqual class Rss(BaseModel): @@ -119,7 +120,7 @@ class RssSpectroscopy(BaseModel): """ grating: Literal["pg0700", "pg0900", "pg1300", "pg1800", "pg2300", "pg3000"] - grating_angle: Angle + grating_angle: Annotated[Angle, GreaterEqual(0 * u.deg), LessEqual(100 * u.deg)] articulation_angle: Angle order_blocking_filter: RssOrderBlockingFilter polarimetry: RssPolarimetry | None = None @@ -127,6 +128,28 @@ class RssSpectroscopy(BaseModel): include_arc: bool = True request_spectrophotometric_standard: bool = False + @field_validator("articulation_angle", mode="after") + @classmethod + def check_articulation_angle(cls, angle: Angle) -> Angle: + error = "The articulation angle must either be 0 deg or a value 1.75 deg + (n - 1) * 0.75 deg with 1 <= n <= 132." + degrees = angle.to(u.deg).value + + if degrees < 0: + raise ValueError(error) + + grace = 1e-6 + if abs(degrees) < grace: + return angle + + n = (degrees - 1.75) / 0.75 + 1 + if n < 1 - grace or n > 132 + grace: + raise ValueError(error) + + if abs(n - round(n)) > grace: + raise ValueError + + return angle + class RssPolarimetry(BaseModel): """ diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 209f474..26aba26 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -18,8 +18,9 @@ SalticamDetector, Rss, RssImaging, + RssPolarimetry, + RssSpectroscopy, ) -from aeonlib.salt.models.rss_models import RssPolarimetry @pytest.fixture() @@ -128,3 +129,17 @@ def base_rss_polarimetry(): @pytest.fixture() def base_rss_imaging(): return RssImaging(filter="pi04400", polarimetry=None, include_flat=True) + + +@pytest.fixture() +def base_rss_spectroscopy(base_rss_polarimetry): + return RssSpectroscopy( + grating="pg0900", + grating_angle=20 * u.deg, + articulation_angle=40 * u.deg, + order_blocking_filter="pc04600", + polarimetry=base_rss_polarimetry, + include_flat=True, + include_arc=True, + request_spectrophotometric_standard=False, + ) diff --git a/tests/salt/models/test_rss_models.py b/tests/salt/models/test_rss_models.py index 9639e61..cc6f7f0 100644 --- a/tests/salt/models/test_rss_models.py +++ b/tests/salt/models/test_rss_models.py @@ -4,7 +4,7 @@ import pytest from astropy import units as u -from aeonlib.salt.models import RssPolarimetry +from aeonlib.salt.models import RssPolarimetry, RssSpectroscopy class TestRss: @@ -19,6 +19,37 @@ def test_rss_imaging(self, base_rss_imaging): assert True +class TestRssSpectroscopy: + def test_rss_spectroscopy(self, base_rss_spectroscopy): + """Test that RSS spectroscopy setups can be built.""" + assert True + + @pytest.mark.parametrize( + "angle, expectation", + [ + (-40, pytest.raises(ValueError)), + (0, nullcontext()), + (0.01, pytest.raises(ValueError)), + (0.125, pytest.raises(ValueError)), + (64.73, pytest.raises(ValueError)), + (64.75, nullcontext()), + (64.75 * u.deg, nullcontext()), + ((64.75 * u.deg).to(u.rad), nullcontext()), + (64.76 * u.deg, pytest.raises(ValueError)), + (100, nullcontext()), + (100.75, pytest.raises(ValueError)), + (400, pytest.raises(ValueError)), + ], + ) + def test_articulation_angle_must_have_allowed_value( + self, angle, expectation, base_rss_spectroscopy + ): + spectroscopy = base_rss_spectroscopy.model_dump() + spectroscopy["articulation_angle"] = angle + with expectation: + RssSpectroscopy(**spectroscopy) + + class TestRssPolarimetry: def test_rss_polarimetry(self, base_rss_polarimetry): """Test that RSS polarimetry setups can be built.""" @@ -56,7 +87,7 @@ def test_pattern_must_have_between_1_and_8_steps(self, pattern, expectation): ], ) def test_angles_must_have_allowed_value(self, angle, expectation): - # Test that wave plater pattern angles must be a multiple of 11.25 deg between + # Test that wave plate pattern angles must be a multiple of 11.25 deg between # 0 deg (inclusive) and 360 deg (exclusive). with expectation: RssPolarimetry(wave_plate_pattern=[(angle, 45 * u.deg)]) From c83bc6233179c9795f8d8e4a55e88a3db98f7454 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 9 Nov 2025 14:48:43 +0200 Subject: [PATCH 046/171] Add a model for RSS longslit spectroscopy setups --- src/aeonlib/salt/models/__init__.py | 9 ++++++++- src/aeonlib/salt/models/rss_models.py | 16 ++++++++++++++++ tests/salt/models/conftest.py | 8 ++++++++ tests/salt/models/test_rss_models.py | 6 ++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 38c0330..e11d115 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,5 +1,11 @@ from .target_models import MagnitudeRange, SaltSiderealTarget -from .rss_models import Rss, RssImaging, RssPolarimetry, RssSpectroscopy +from .rss_models import ( + Rss, + RssImaging, + RssLongslitSpectroscopy, + RssPolarimetry, + RssSpectroscopy, +) from .salticam_models import ( SalticamFilterSequenceStep, Salticam, @@ -21,6 +27,7 @@ "Request", "Rss", "RssImaging", + "RssLongslitSpectroscopy", "RssPolarimetry", "RssSpectroscopy", "Salticam", diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 5ade117..3e9da2a 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -151,6 +151,22 @@ def check_articulation_angle(cls, angle: Angle) -> Angle: return angle +class RssLongslitSpectroscopy(RssSpectroscopy): + """ + An RSS longslit spectroscopy setup. + + In addition to the properties required by a generic RSS spectroscopy the user must + specify the barcode of the longslit to use. + + Attributes + ---------- + slit + The barcode of the longslit to use, such as "PL0125N001". + """ + + slit: str + + class RssPolarimetry(BaseModel): """ An RSS polarimetry setup. diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 26aba26..cb8f9f9 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -20,6 +20,7 @@ RssImaging, RssPolarimetry, RssSpectroscopy, + RssLongslitSpectroscopy, ) @@ -143,3 +144,10 @@ def base_rss_spectroscopy(base_rss_polarimetry): include_arc=True, request_spectrophotometric_standard=False, ) + + +@pytest.fixture() +def base_rss_longslit_spectroscopy(base_rss_spectroscopy): + return RssLongslitSpectroscopy( + **base_rss_spectroscopy.model_dump(), slit="PL0125N001" + ) diff --git a/tests/salt/models/test_rss_models.py b/tests/salt/models/test_rss_models.py index cc6f7f0..13abcef 100644 --- a/tests/salt/models/test_rss_models.py +++ b/tests/salt/models/test_rss_models.py @@ -92,3 +92,9 @@ def test_angles_must_have_allowed_value(self, angle, expectation): with expectation: RssPolarimetry(wave_plate_pattern=[(angle, 45 * u.deg)]) RssPolarimetry(wave_plate_pattern=[(45 * u.deg, angle)]) + + +class TestRssLongslitSpectroscopy: + def test_rss_longslit_spectroscopy(self, base_rss_longslit_spectroscopy): + # Test that RSS longslit spectroscopy setups can be built. + assert True From 5f98e67c9dde0e333792a997a14a4320113cb35d Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 9 Nov 2025 15:37:53 +0200 Subject: [PATCH 047/171] Add a model for RSS multiobject spectroscopy (MOS) setups --- src/aeonlib/salt/models/__init__.py | 2 ++ src/aeonlib/salt/models/rss_models.py | 19 ++++++++++++++++++- tests/salt/data/dummy_rss_mos_mask.rsim | 0 tests/salt/models/conftest.py | 9 +++++++++ tests/salt/models/test_rss_models.py | 6 ++++++ 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/salt/data/dummy_rss_mos_mask.rsim diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index e11d115..9dd2cfb 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -3,6 +3,7 @@ Rss, RssImaging, RssLongslitSpectroscopy, + RssMultiObjectSpectroscopy, RssPolarimetry, RssSpectroscopy, ) @@ -28,6 +29,7 @@ "Rss", "RssImaging", "RssLongslitSpectroscopy", + "RssMultiObjectSpectroscopy", "RssPolarimetry", "RssSpectroscopy", "Salticam", diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 3e9da2a..6a64550 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -3,7 +3,7 @@ from typing import Literal, Annotated from astropy import units as u -from pydantic import BaseModel, PositiveInt, field_validator +from pydantic import BaseModel, FilePath, PositiveInt, field_validator from aeonlib.types import Angle from aeonlib.salt.models.types.filters import ( @@ -167,6 +167,23 @@ class RssLongslitSpectroscopy(RssSpectroscopy): slit: str +class RssMultiObjectSpectroscopy(RssSpectroscopy): + """ + An RSS multiobject spectroscopy (MOS) setup. + + In addition to the properties required by a generic RSS spectroscopy the user must + specify the path of the file describing the MOS mask. The path must exist and must + be a file. + + Attributes + ---------- + mask + The file path of the file describing the MOS mask. + """ + + mask: FilePath + + class RssPolarimetry(BaseModel): """ An RSS polarimetry setup. diff --git a/tests/salt/data/dummy_rss_mos_mask.rsim b/tests/salt/data/dummy_rss_mos_mask.rsim new file mode 100644 index 0000000..e69de29 diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index cb8f9f9..cb6a775 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -21,6 +21,7 @@ RssPolarimetry, RssSpectroscopy, RssLongslitSpectroscopy, + RssMultiObjectSpectroscopy, ) @@ -151,3 +152,11 @@ def base_rss_longslit_spectroscopy(base_rss_spectroscopy): return RssLongslitSpectroscopy( **base_rss_spectroscopy.model_dump(), slit="PL0125N001" ) + + +@pytest.fixture() +def base_rss_multi_object_spectroscopy(base_rss_spectroscopy): + return RssMultiObjectSpectroscopy( + **base_rss_spectroscopy.model_dump(), + mask=pathlib.Path(__file__).parent.parent / "data" / "dummy_rss_mos_mask.rsim", + ) diff --git a/tests/salt/models/test_rss_models.py b/tests/salt/models/test_rss_models.py index 13abcef..bc341d8 100644 --- a/tests/salt/models/test_rss_models.py +++ b/tests/salt/models/test_rss_models.py @@ -98,3 +98,9 @@ class TestRssLongslitSpectroscopy: def test_rss_longslit_spectroscopy(self, base_rss_longslit_spectroscopy): # Test that RSS longslit spectroscopy setups can be built. assert True + + +class TestRssMultiObjectSpectroscopy: + def test_rss_multi_object_spectroscopy(self, base_rss_multi_object_spectroscopy): + # Test that RSS multiobject spectroscopy setups can be built. + assert True From 948883d85b038450d5f25fbe2cea08c22b887aef Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 9 Nov 2025 15:56:18 +0200 Subject: [PATCH 048/171] Refactor RSS and Salticam types --- src/aeonlib/salt/models/rss_models.py | 7 ++-- src/aeonlib/salt/models/types/__init__.py | 6 +++- .../salt/models/types/{filters.py => rss.py} | 32 +++---------------- src/aeonlib/salt/models/types/salticam.py | 29 +++++++++++++++++ 4 files changed, 42 insertions(+), 32 deletions(-) rename src/aeonlib/salt/models/types/{filters.py => rss.py} (64%) create mode 100644 src/aeonlib/salt/models/types/salticam.py diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 6a64550..26d7ef3 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -6,7 +6,8 @@ from pydantic import BaseModel, FilePath, PositiveInt, field_validator from aeonlib.types import Angle -from aeonlib.salt.models.types.filters import ( +from aeonlib.salt.models.types import ( + RssGrating, RssImagingFilter, RssOrderBlockingFilter, SalticamFilter, @@ -119,7 +120,7 @@ class RssSpectroscopy(BaseModel): """ - grating: Literal["pg0700", "pg0900", "pg1300", "pg1800", "pg2300", "pg3000"] + grating: RssGrating grating_angle: Annotated[Angle, GreaterEqual(0 * u.deg), LessEqual(100 * u.deg)] articulation_angle: Angle order_blocking_filter: RssOrderBlockingFilter @@ -161,7 +162,7 @@ class RssLongslitSpectroscopy(RssSpectroscopy): Attributes ---------- slit - The barcode of the longslit to use, such as "PL0125N001". + The barcode of the longslit, such as "PL0125N001". """ slit: str diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py index d99d239..1ffe90e 100644 --- a/src/aeonlib/salt/models/types/__init__.py +++ b/src/aeonlib/salt/models/types/__init__.py @@ -1,7 +1,8 @@ from .quantity import AstropyQuantityTypeAnnotation from .block import SkyTransparency from .duration import Duration, PositiveDuration -from .filters import SalticamFilter +from .rss import RssGrating, RssImagingFilter, RssOrderBlockingFilter +from .salticam import SalticamFilter from .target import MagnitudeBandpass, TargetType __all__ = [ @@ -9,6 +10,9 @@ "Duration", "MagnitudeBandpass", "PositiveDuration", + "RssGrating", + "RssImagingFilter", + "RssOrderBlockingFilter", "SalticamFilter", "SkyTransparency", "TargetType", diff --git a/src/aeonlib/salt/models/types/filters.py b/src/aeonlib/salt/models/types/rss.py similarity index 64% rename from src/aeonlib/salt/models/types/filters.py rename to src/aeonlib/salt/models/types/rss.py index 6465ff1..2ff2f0b 100644 --- a/src/aeonlib/salt/models/types/filters.py +++ b/src/aeonlib/salt/models/types/rss.py @@ -1,33 +1,5 @@ from typing import Literal -SalticamFilter = Literal[ - "Fused silica clear", - "Johnson U", - "Johnson B", - "Johnson V", - "Cousins R", - "Cousins I", - "380nm 40nm FWHM", - "SDSS u'", - "SDSS g'", - "SDSS r'", - "SDSS i'", - "SDSS z'", - "H-alpha", - "H-beta narrow", - "H-beta wide", - "Stroemgren u", - "Stroemgren v", - "Stroemgren b", - "Stroemgren y", - "SRE 1", - "SRE 2", - "SRE 3", - "SRE 4", -] -"""A filter for Salticam.""" - - RssImagingFilter = Literal[ "pi04340", "pi04400", @@ -75,3 +47,7 @@ RssOrderBlockingFilter = Literal["pc00000", "pc03200", "pc03400", "pc03850", "pc04600"] """An order blocking filter for RSS.""" + + +RssGrating = Literal["pg0700", "pg0900", "pg1300", "pg1800", "pg2300", "pg3000"] +"""An RSS grating.""" diff --git a/src/aeonlib/salt/models/types/salticam.py b/src/aeonlib/salt/models/types/salticam.py new file mode 100644 index 0000000..dd14d6f --- /dev/null +++ b/src/aeonlib/salt/models/types/salticam.py @@ -0,0 +1,29 @@ +from typing import Literal + + +SalticamFilter = Literal[ + "Fused silica clear", + "Johnson U", + "Johnson B", + "Johnson V", + "Cousins R", + "Cousins I", + "380nm 40nm FWHM", + "SDSS u'", + "SDSS g'", + "SDSS r'", + "SDSS i'", + "SDSS z'", + "H-alpha", + "H-beta narrow", + "H-beta wide", + "Stroemgren u", + "Stroemgren v", + "Stroemgren b", + "Stroemgren y", + "SRE 1", + "SRE 2", + "SRE 3", + "SRE 4", +] +"""A filter for Salticam.""" From 7f7548a8ce4baee3d10207d649fa3ee33ac6cac4 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 9 Nov 2025 16:04:51 +0200 Subject: [PATCH 049/171] Add a model for RSS slit mask IFU spectroscopy setups --- src/aeonlib/salt/models/__init__.py | 2 ++ src/aeonlib/salt/models/rss_models.py | 17 +++++++++++++++++ src/aeonlib/salt/models/types/__init__.py | 3 ++- src/aeonlib/salt/models/types/rss.py | 4 ++++ tests/salt/models/conftest.py | 9 +++++++++ tests/salt/models/test_rss_models.py | 6 ++++++ 6 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 9dd2cfb..988d48c 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -5,6 +5,7 @@ RssLongslitSpectroscopy, RssMultiObjectSpectroscopy, RssPolarimetry, + RssSlitMaskIFUSpectroscopy, RssSpectroscopy, ) from .salticam_models import ( @@ -31,6 +32,7 @@ "RssLongslitSpectroscopy", "RssMultiObjectSpectroscopy", "RssPolarimetry", + "RssSlitMaskIFUSpectroscopy", "RssSpectroscopy", "Salticam", "SalticamDetector", diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 26d7ef3..c221f77 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -10,6 +10,7 @@ RssGrating, RssImagingFilter, RssOrderBlockingFilter, + RssSlitMaskIFU, SalticamFilter, ) from aeonlib.salt.validators import GreaterEqual, LessEqual @@ -185,6 +186,22 @@ class RssMultiObjectSpectroscopy(RssSpectroscopy): mask: FilePath +class RssSlitMaskIFUSpectroscopy(RssSpectroscopy): + """ + An RSS slit mask integrated field unit (IFU) setup. + + In addition to the properties required by a generic RSS spectroscopy the user must + specify the barcode of the slit mask IFU to use. + + Attributes + ---------- + slit_mask_ifu + The barcode of the slit mask IFU, such as "PF0200N001". + """ + + slit_mask_ifu: RssSlitMaskIFU + + class RssPolarimetry(BaseModel): """ An RSS polarimetry setup. diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py index 1ffe90e..3892b69 100644 --- a/src/aeonlib/salt/models/types/__init__.py +++ b/src/aeonlib/salt/models/types/__init__.py @@ -1,7 +1,7 @@ from .quantity import AstropyQuantityTypeAnnotation from .block import SkyTransparency from .duration import Duration, PositiveDuration -from .rss import RssGrating, RssImagingFilter, RssOrderBlockingFilter +from .rss import RssGrating, RssImagingFilter, RssOrderBlockingFilter, RssSlitMaskIFU from .salticam import SalticamFilter from .target import MagnitudeBandpass, TargetType @@ -13,6 +13,7 @@ "RssGrating", "RssImagingFilter", "RssOrderBlockingFilter", + "RssSlitMaskIFU", "SalticamFilter", "SkyTransparency", "TargetType", diff --git a/src/aeonlib/salt/models/types/rss.py b/src/aeonlib/salt/models/types/rss.py index 2ff2f0b..32ca491 100644 --- a/src/aeonlib/salt/models/types/rss.py +++ b/src/aeonlib/salt/models/types/rss.py @@ -51,3 +51,7 @@ RssGrating = Literal["pg0700", "pg0900", "pg1300", "pg1800", "pg2300", "pg3000"] """An RSS grating.""" + + +RssSlitMaskIFU = Literal["PF0200N001"] +"""A slit mask integrated field unit (IFU) for RSS.""" diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index cb6a775..7124e60 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -22,6 +22,7 @@ RssSpectroscopy, RssLongslitSpectroscopy, RssMultiObjectSpectroscopy, + RssSlitMaskIFUSpectroscopy, ) @@ -160,3 +161,11 @@ def base_rss_multi_object_spectroscopy(base_rss_spectroscopy): **base_rss_spectroscopy.model_dump(), mask=pathlib.Path(__file__).parent.parent / "data" / "dummy_rss_mos_mask.rsim", ) + + +@pytest.fixture() +def base_rss_slit_mask_ifu_spectroscopy(base_rss_spectroscopy): + return RssSlitMaskIFUSpectroscopy( + **base_rss_spectroscopy.model_dump(), + slit_mask_ifu="PF0200N001", + ) diff --git a/tests/salt/models/test_rss_models.py b/tests/salt/models/test_rss_models.py index bc341d8..1eac124 100644 --- a/tests/salt/models/test_rss_models.py +++ b/tests/salt/models/test_rss_models.py @@ -104,3 +104,9 @@ class TestRssMultiObjectSpectroscopy: def test_rss_multi_object_spectroscopy(self, base_rss_multi_object_spectroscopy): # Test that RSS multiobject spectroscopy setups can be built. assert True + + +class TestRssSlitMaskIFUSpectroscopy: + def test_slit_mask_ifu_spectroscopy(self, base_rss_slit_mask_ifu_spectroscopy): + # Test that RSS slit mask IFU spectroscopy setups can be built. + assert True From a325eae80a5b076d6147ac8532b85cd16e70419f Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 9 Nov 2025 16:09:25 +0200 Subject: [PATCH 050/171] Update the type of the configuration field in the Rss model --- src/aeonlib/salt/models/rss_models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index c221f77..7c72cc7 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -61,7 +61,12 @@ class Rss(BaseModel): """ num_cycles: PositiveInt = 1 - configuration: RssImaging + configuration: ( + RssImaging + | RssLongslitSpectroscopy + | RssMultiObjectSpectroscopy + | RssSlitMaskIFUSpectroscopy + ) detector: None dither_pattern: None From ed7ff52ac0f4571b7960ce9f950112182ae5e637 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 9 Nov 2025 17:02:53 +0200 Subject: [PATCH 051/171] Add a model for RSS detector setups --- src/aeonlib/salt/models/__init__.py | 2 + src/aeonlib/salt/models/rss_models.py | 47 ++++++++++++++++++++++- src/aeonlib/salt/models/types/__init__.py | 13 ++++++- src/aeonlib/salt/models/types/rss.py | 9 +++++ tests/salt/models/conftest.py | 19 ++++++++- tests/salt/models/test_rss_models.py | 6 +++ 6 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 988d48c..1148917 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,6 +1,7 @@ from .target_models import MagnitudeRange, SaltSiderealTarget from .rss_models import ( Rss, + RssDetector, RssImaging, RssLongslitSpectroscopy, RssMultiObjectSpectroscopy, @@ -28,6 +29,7 @@ "ReferenceStar", "Request", "Rss", + "RssDetector", "RssImaging", "RssLongslitSpectroscopy", "RssMultiObjectSpectroscopy", diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 7c72cc7..2b37c95 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -7,13 +7,17 @@ from aeonlib.types import Angle from aeonlib.salt.models.types import ( + PositiveDuration, + RssGain, RssGrating, RssImagingFilter, RssOrderBlockingFilter, + RssReadoutMode, + RssReadoutSpeed, RssSlitMaskIFU, SalticamFilter, ) -from aeonlib.salt.validators import GreaterEqual, LessEqual +from aeonlib.salt.validators import GreaterEqual, GreaterThan, LessEqual class Rss(BaseModel): @@ -67,7 +71,7 @@ class Rss(BaseModel): | RssMultiObjectSpectroscopy | RssSlitMaskIFUSpectroscopy ) - detector: None + detector: RssDetector dither_pattern: None @@ -282,3 +286,42 @@ def check_angle_values(cls, value: _WavePlatePattern) -> _WavePlatePattern: RssPolarimetry._check_pattern_step(step) return value + + +class RssDetector(BaseModel): + """ + An Rss detector setup. + + Attributes + ---------- + exposure_time + The exposure time. If multiple exposures are requested, this is the time per + exposure. + num_exposures + The number of exposures to take. + readout_mode + The readout mode. + gain + The gain. + readout_speed + The readout speed. + num_prebinned_rows + The number of prebinned rows, which must be between 1 and 9 (both inclusive). + num_prebinned_columns + The number of prebinned columns, which must be between 1 and 9 (both inclusive). + window_height + The height of the detector window, which must be a positive angle less than + or equal to 518 arcseconds. In most cases there is no need to define a + detector window. + """ + + exposure_time: PositiveDuration + num_exposures: int = 1 + readout_mode: RssReadoutMode = "normal" + gain: RssGain + readout_speed: RssReadoutSpeed + num_prebinned_rows: Annotated[int, GreaterEqual(1), LessEqual(9)] + num_prebinned_columns: Annotated[int, GreaterEqual(1), LessEqual(9)] + window_height: Annotated[ + Angle | None, GreaterThan(0 * u.arcsec), LessEqual(518 * u.arcsec) + ] = None diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py index 3892b69..fb6281e 100644 --- a/src/aeonlib/salt/models/types/__init__.py +++ b/src/aeonlib/salt/models/types/__init__.py @@ -1,7 +1,15 @@ from .quantity import AstropyQuantityTypeAnnotation from .block import SkyTransparency from .duration import Duration, PositiveDuration -from .rss import RssGrating, RssImagingFilter, RssOrderBlockingFilter, RssSlitMaskIFU +from .rss import ( + RssGain, + RssGrating, + RssImagingFilter, + RssOrderBlockingFilter, + RssReadoutMode, + RssReadoutSpeed, + RssSlitMaskIFU, +) from .salticam import SalticamFilter from .target import MagnitudeBandpass, TargetType @@ -10,9 +18,12 @@ "Duration", "MagnitudeBandpass", "PositiveDuration", + "RssGain", "RssGrating", "RssImagingFilter", "RssOrderBlockingFilter", + "RssReadoutMode", + "RssReadoutSpeed", "RssSlitMaskIFU", "SalticamFilter", "SkyTransparency", diff --git a/src/aeonlib/salt/models/types/rss.py b/src/aeonlib/salt/models/types/rss.py index 32ca491..d0ac04b 100644 --- a/src/aeonlib/salt/models/types/rss.py +++ b/src/aeonlib/salt/models/types/rss.py @@ -55,3 +55,12 @@ RssSlitMaskIFU = Literal["PF0200N001"] """A slit mask integrated field unit (IFU) for RSS.""" + +RssReadoutMode = Literal["normal", "frame transfer", "slot mode"] +"""An RSS readout mode.""" + +RssGain = Literal["faint", "bright"] +"""An RSS detector gain.""" + +RssReadoutSpeed = Literal["fast", "slow"] +"""An RSS detector readout speed.""" diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 7124e60..db0190d 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -17,6 +17,7 @@ SalticamFilterSequenceStep, SalticamDetector, Rss, + RssDetector, RssImaging, RssPolarimetry, RssSpectroscopy, @@ -120,8 +121,10 @@ def base_salticam_dither_pattern(): @pytest.fixture() -def base_rss(base_rss_imaging): - return Rss(configuration=base_rss_imaging, detector=None, dither_pattern=None) +def base_rss(base_rss_imaging, base_rss_detector): + return Rss( + configuration=base_rss_imaging, detector=base_rss_detector, dither_pattern=None + ) @pytest.fixture() @@ -169,3 +172,15 @@ def base_rss_slit_mask_ifu_spectroscopy(base_rss_spectroscopy): **base_rss_spectroscopy.model_dump(), slit_mask_ifu="PF0200N001", ) + + +@pytest.fixture() +def base_rss_detector(): + return RssDetector( + exposure_time=120 * u.s, + gain="bright", + readout_speed="fast", + num_prebinned_rows=2, + num_prebinned_columns=2, + window_height=100 * u.arcsec, + ) diff --git a/tests/salt/models/test_rss_models.py b/tests/salt/models/test_rss_models.py index 1eac124..b74f7b2 100644 --- a/tests/salt/models/test_rss_models.py +++ b/tests/salt/models/test_rss_models.py @@ -110,3 +110,9 @@ class TestRssSlitMaskIFUSpectroscopy: def test_slit_mask_ifu_spectroscopy(self, base_rss_slit_mask_ifu_spectroscopy): # Test that RSS slit mask IFU spectroscopy setups can be built. assert True + + +class TestRssDetector: + def test_rss_detector(self, base_rss_detector): + # Test that RSS detector setups can be built. + assert True From acff1b7cb6fc20e87ae7fffc06a2f6ef18468793 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 9 Nov 2025 17:24:32 +0200 Subject: [PATCH 052/171] Enforce a positive dither pattern offset --- src/aeonlib/salt/models/salticam_models.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index 37ba71c..3bd3349 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -12,7 +12,7 @@ SalticamFilter, AstropyQuantityTypeAnnotation, ) -from aeonlib.salt.validators import GreaterEqual, LessEqual +from aeonlib.salt.validators import GreaterEqual, GreaterThan, LessEqual class Salticam(BaseModel): @@ -93,6 +93,11 @@ class SalticamDetector(BaseModel): num_prebinned_columns: Annotated[int, GreaterEqual(1), LessEqual(9)] +_Offset = Annotated[ + Union[Quantity, float], AstropyQuantityTypeAnnotation(default_unit=u.arcsec) +] + + class SalticamDitherPattern(BaseModel): """ A dither pattern for Salticam. @@ -124,9 +129,7 @@ class SalticamDitherPattern(BaseModel): num_steps: PositiveInt = Field( default_factory=lambda data: data["num_rows"] * data["num_columns"] ) - offset: Annotated[ - Union[Quantity, float], AstropyQuantityTypeAnnotation(default_unit=u.arcsec) - ] + offset: Annotated[_Offset, GreaterThan(0)] @model_validator(mode="after") def check_number_of_steps(self): From 034b418b0bb51adefb7f450f30d2ffc58ec4011b Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 9 Nov 2025 17:38:35 +0200 Subject: [PATCH 053/171] Add a model for RSS dither patterns --- src/aeonlib/salt/models/__init__.py | 2 + src/aeonlib/salt/models/rss_models.py | 63 +++++++++++++++++++++++++-- tests/salt/models/conftest.py | 12 ++++- tests/salt/models/test_rss_models.py | 45 ++++++++++++++++++- 4 files changed, 116 insertions(+), 6 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 1148917..645ce87 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -2,6 +2,7 @@ from .rss_models import ( Rss, RssDetector, + RssDitherPattern, RssImaging, RssLongslitSpectroscopy, RssMultiObjectSpectroscopy, @@ -30,6 +31,7 @@ "Request", "Rss", "RssDetector", + "RssDitherPattern", "RssImaging", "RssLongslitSpectroscopy", "RssMultiObjectSpectroscopy", diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 2b37c95..b33c910 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -1,9 +1,17 @@ from __future__ import annotations -from typing import Literal, Annotated +from typing import Literal, Annotated, Union from astropy import units as u -from pydantic import BaseModel, FilePath, PositiveInt, field_validator +from astropy.units import Quantity +from pydantic import ( + BaseModel, + Field, + FilePath, + PositiveInt, + field_validator, + model_validator, +) from aeonlib.types import Angle from aeonlib.salt.models.types import ( @@ -16,6 +24,7 @@ RssReadoutSpeed, RssSlitMaskIFU, SalticamFilter, + AstropyQuantityTypeAnnotation, ) from aeonlib.salt.validators import GreaterEqual, GreaterThan, LessEqual @@ -72,7 +81,7 @@ class Rss(BaseModel): | RssSlitMaskIFUSpectroscopy ) detector: RssDetector - dither_pattern: None + dither_pattern: RssDitherPattern | None class RssImaging(BaseModel): @@ -325,3 +334,51 @@ class RssDetector(BaseModel): window_height: Annotated[ Angle | None, GreaterThan(0 * u.arcsec), LessEqual(518 * u.arcsec) ] = None + + +_Offset = Annotated[ + Union[Quantity, float], AstropyQuantityTypeAnnotation(default_unit=u.arcsec) +] + + +class RssDitherPattern(BaseModel): + """ + A dither pattern for RSS. + + The dither pattern is characterised by the number of rows and columns it covers, + the number of steps to take, and the offset between the steps. + + By default, the number of steps is the product of rows and columns, but you may + specify a multiple of that number if you want to perform the pattern more than once. + + The offset is in detector coordinates, not in right ascension and declination. + Therefore, if a particular object orientation is desired, a suitable position + angle must be chosen so that the dithers coincide with the detector axes. + + Attributes + ---------- + num_rows + Number of rows in the pattern. + num_columns + Number of columns in the pattern. + number_steps + Number of steps to perform. + offset + Offset between steps, as a `astropy.units.Quantity` or as a float in arcsec. + """ + + num_rows: PositiveInt + num_columns: PositiveInt + num_steps: PositiveInt = Field( + default_factory=lambda data: data["num_rows"] * data["num_columns"] + ) + offset: Annotated[_Offset, GreaterThan(0)] + + @model_validator(mode="after") + def check_number_of_steps(self): + if self.num_steps % (self.num_rows * self.num_columns) != 0: + raise ValueError( + "The number of steps must be the number of rows times the number of " + "columns, or a multiple thereof." + ) + return self diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index db0190d..954df8d 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -18,6 +18,7 @@ SalticamDetector, Rss, RssDetector, + RssDitherPattern, RssImaging, RssPolarimetry, RssSpectroscopy, @@ -121,9 +122,11 @@ def base_salticam_dither_pattern(): @pytest.fixture() -def base_rss(base_rss_imaging, base_rss_detector): +def base_rss(base_rss_imaging, base_rss_detector, base_rss_dither_pattern): return Rss( - configuration=base_rss_imaging, detector=base_rss_detector, dither_pattern=None + configuration=base_rss_imaging, + detector=base_rss_detector, + dither_pattern=base_rss_dither_pattern, ) @@ -184,3 +187,8 @@ def base_rss_detector(): num_prebinned_columns=2, window_height=100 * u.arcsec, ) + + +@pytest.fixture() +def base_rss_dither_pattern(): + return RssDitherPattern(num_rows=3, num_columns=4, offset=12.9) diff --git a/tests/salt/models/test_rss_models.py b/tests/salt/models/test_rss_models.py index b74f7b2..ec9168c 100644 --- a/tests/salt/models/test_rss_models.py +++ b/tests/salt/models/test_rss_models.py @@ -3,8 +3,9 @@ import pytest from astropy import units as u +from pydantic import ValidationError -from aeonlib.salt.models import RssPolarimetry, RssSpectroscopy +from aeonlib.salt.models import RssDitherPattern, RssPolarimetry, RssSpectroscopy class TestRss: @@ -116,3 +117,45 @@ class TestRssDetector: def test_rss_detector(self, base_rss_detector): # Test that RSS detector setups can be built. assert True + + +class TestRssDitherPattern: + def test_rss_dither_pattern(self, base_rss_dither_pattern): + """Test that RSS dither pattern can be built.""" + assert True + + def test_default_number_of_steps(self, base_rss_dither_pattern): + dither_pattern = base_rss_dither_pattern.model_dump() + dither_pattern["num_rows"] = 4 + dither_pattern["num_columns"] = 3 + if "num_steps" in dither_pattern: + del dither_pattern["num_steps"] + assert RssDitherPattern(**dither_pattern).num_steps == 12 # type: ignore + + @pytest.mark.parametrize( + "num_rows, num_columns, num_steps, expectation", + [ + (1, 1, 1, nullcontext()), + (1, 1, 5, nullcontext()), + (1, 2, 2, nullcontext()), + (2, 1, 6, nullcontext()), + (3, 5, 15, nullcontext()), + (5, 3, 45, nullcontext()), + (5, 2, 9, pytest.raises(ValidationError)), + (3, 7, 43, pytest.raises(ValidationError)), + ], + ) + def test_only_complete_patterns_allowed( + self, + num_rows, + num_columns, + num_steps, + expectation, + base_rss_dither_pattern, + ): + dither_pattern = base_rss_dither_pattern.model_dump() + dither_pattern["num_rows"] = num_rows + dither_pattern["num_columns"] = num_columns + dither_pattern["num_steps"] = num_steps + with expectation: + RssDitherPattern(**dither_pattern) # type: ignore From 1fcd3b0c3cedd86c3c7ddcc23bb6c7ab395807ce Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 9 Nov 2025 19:42:51 +0200 Subject: [PATCH 054/171] Do not limit dither pattern offsets to positive values --- src/aeonlib/salt/models/rss_models.py | 9 +++------ src/aeonlib/salt/models/salticam_models.py | 11 ++++------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index b33c910..dcd65f4 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -336,11 +336,6 @@ class RssDetector(BaseModel): ] = None -_Offset = Annotated[ - Union[Quantity, float], AstropyQuantityTypeAnnotation(default_unit=u.arcsec) -] - - class RssDitherPattern(BaseModel): """ A dither pattern for RSS. @@ -372,7 +367,9 @@ class RssDitherPattern(BaseModel): num_steps: PositiveInt = Field( default_factory=lambda data: data["num_rows"] * data["num_columns"] ) - offset: Annotated[_Offset, GreaterThan(0)] + offset: Annotated[ + Union[Quantity, float], AstropyQuantityTypeAnnotation(default_unit=u.arcsec) + ] @model_validator(mode="after") def check_number_of_steps(self): diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index 3bd3349..37ba71c 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -12,7 +12,7 @@ SalticamFilter, AstropyQuantityTypeAnnotation, ) -from aeonlib.salt.validators import GreaterEqual, GreaterThan, LessEqual +from aeonlib.salt.validators import GreaterEqual, LessEqual class Salticam(BaseModel): @@ -93,11 +93,6 @@ class SalticamDetector(BaseModel): num_prebinned_columns: Annotated[int, GreaterEqual(1), LessEqual(9)] -_Offset = Annotated[ - Union[Quantity, float], AstropyQuantityTypeAnnotation(default_unit=u.arcsec) -] - - class SalticamDitherPattern(BaseModel): """ A dither pattern for Salticam. @@ -129,7 +124,9 @@ class SalticamDitherPattern(BaseModel): num_steps: PositiveInt = Field( default_factory=lambda data: data["num_rows"] * data["num_columns"] ) - offset: Annotated[_Offset, GreaterThan(0)] + offset: Annotated[ + Union[Quantity, float], AstropyQuantityTypeAnnotation(default_unit=u.arcsec) + ] @model_validator(mode="after") def check_number_of_steps(self): From be2dc5b4d519cd829a172a3b9b4cd0f887ca679e Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 9 Nov 2025 21:22:23 +0200 Subject: [PATCH 055/171] Add a model for HRS configurations --- src/aeonlib/salt/models/__init__.py | 2 + src/aeonlib/salt/models/hrs_models.py | 65 +++++++++++++++++++++++ src/aeonlib/salt/models/types/__init__.py | 3 ++ src/aeonlib/salt/models/types/hrs.py | 10 ++++ tests/salt/models/conftest.py | 6 +++ tests/salt/models/test_hrs_models.py | 65 +++++++++++++++++++++++ 6 files changed, 151 insertions(+) create mode 100644 src/aeonlib/salt/models/hrs_models.py create mode 100644 src/aeonlib/salt/models/types/hrs.py create mode 100644 tests/salt/models/test_hrs_models.py diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 645ce87..9e4f762 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,4 +1,5 @@ from .target_models import MagnitudeRange, SaltSiderealTarget +from .hrs_models import Hrs from .rss_models import ( Rss, RssDetector, @@ -24,6 +25,7 @@ "Acquisition", "Block", "Constraints", + "Hrs", "SalticamDitherPattern", "SalticamFilterSequenceStep", "MagnitudeRange", diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py new file mode 100644 index 0000000..90b6266 --- /dev/null +++ b/src/aeonlib/salt/models/hrs_models.py @@ -0,0 +1,65 @@ +from typing import Annotated, Self + +from astropy import units as u +from pydantic import BaseModel, Field, PositiveInt, model_validator + +from aeonlib.salt.models.types import HrsMode, HrsPrvCalibration +from aeonlib.salt.validators import GreaterEqual, LessEqual +from aeonlib.types import Angle + + +class Hrs(BaseModel): + """ + An HRS setup. + + HRS can be used in any of four modes, namely low resolution, medium resolution, high + resolution and high stability. A high precision velocity calibration (using a ThAr + kamp) is available for the high stability mode, but not for any of the other modes. + + A sequence of exposure times can be defined for the red and blue detector arm. If + this sequence shall be executed more than once, a number of cycles need to be set. + + Attributes + ---------- + num_cycles + How often the exposure time patterns shall be executed. + mode + The instrument mode, such low resolution or high stability. + prv_calibration + The high precision velocity calibration to use. This must be None for all modes + other than high stability, for which it must be "ThAr". + fibre_separation + The angle between the target and sky fibres. This must be between 16 and 63 + arcseconds (both inclusive). + blue_arm: None + The detector setup for the red arm. + red_arm: None + The detector setup for the blue arm. + """ + + num_cycles: PositiveInt = 1 + mode: HrsMode + prv_calibration: HrsPrvCalibration | None = Field( + default_factory=lambda data: ( + "ThAr" if data["mode"] == "high stability" else None + ) + ) + fibre_separation: Annotated[ + Angle, GreaterEqual(16 * u.arcsec), LessEqual(63 * u.arcsec) + ] = 60 * u.arcsec + blue_arm: None + red_arm: None + + @model_validator(mode="after") + def check_prv_calibration(self) -> Self: + if self.mode == "high stability": + if self.prv_calibration != "ThAr": + raise ValueError( + 'prv_calibration must be "ThAr" for the high stability mode.' + ) + else: + if self.prv_calibration is not None: + raise ValueError( + f"prv_calibration must be None for the {self.mode} mode." + ) + return self diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py index fb6281e..752104b 100644 --- a/src/aeonlib/salt/models/types/__init__.py +++ b/src/aeonlib/salt/models/types/__init__.py @@ -1,6 +1,7 @@ from .quantity import AstropyQuantityTypeAnnotation from .block import SkyTransparency from .duration import Duration, PositiveDuration +from .hrs import HrsMode, HrsPrvCalibration from .rss import ( RssGain, RssGrating, @@ -16,6 +17,8 @@ __all__ = [ "AstropyQuantityTypeAnnotation", "Duration", + "HrsMode", + "HrsPrvCalibration", "MagnitudeBandpass", "PositiveDuration", "RssGain", diff --git a/src/aeonlib/salt/models/types/hrs.py b/src/aeonlib/salt/models/types/hrs.py new file mode 100644 index 0000000..8bb9634 --- /dev/null +++ b/src/aeonlib/salt/models/types/hrs.py @@ -0,0 +1,10 @@ +from typing import Literal + +HrsMode = Literal[ + "low resolution", "medium resolution", "high resolution", "high stability" +] +"""An HRS instrument mode.""" + + +HrsPrvCalibration = Literal["ThAr"] +"""An HRS precision radial velocity calibration.""" diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 954df8d..b40b01b 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -8,6 +8,7 @@ Acquisition, Block, Constraints, + Hrs, MagnitudeRange, ReferenceStar, Request, @@ -192,3 +193,8 @@ def base_rss_detector(): @pytest.fixture() def base_rss_dither_pattern(): return RssDitherPattern(num_rows=3, num_columns=4, offset=12.9) + + +@pytest.fixture() +def base_hrs(): + return Hrs(mode="medium resolution", blue_arm=None, red_arm=None) diff --git a/tests/salt/models/test_hrs_models.py b/tests/salt/models/test_hrs_models.py new file mode 100644 index 0000000..f002771 --- /dev/null +++ b/tests/salt/models/test_hrs_models.py @@ -0,0 +1,65 @@ +from contextlib import nullcontext + +import pytest + +from aeonlib.salt.models import Hrs +from aeonlib.salt.models.types import HrsMode + + +class TestHrs: + def test_hrs(self, base_hrs): + # Test that HRS configurations can be built. + assert True + + @pytest.mark.parametrize( + "mode, prv_calibration", + [ + ("low resolution", None), + ("medium resolution", None), + ("high resolution", None), + ("high stability", "ThAr"), + ], + ) + def test_default_prv_calibration(self, mode: HrsMode, prv_calibration, base_hrs): + # Test that the default value for the precision radial velocity calibration is + # correct. + hrs = Hrs( + num_cycles=base_hrs.num_cycles, + mode=mode, + fibre_separation=base_hrs.fibre_separation, + blue_arm=base_hrs.blue_arm, + red_arm=base_hrs.red_arm, + ) + if prv_calibration is not None: + assert hrs.prv_calibration == prv_calibration + else: + assert hrs.prv_calibration is None + + @pytest.mark.parametrize( + "mode, prv_calibration_is_none", + [ + ("low resolution", True), + ("medium resolution", True), + ("high resolution", True), + ("high stability", False), + ], + ) + def test_allowed_prv_calibration_depends_on_mode( + self, mode: HrsMode, prv_calibration_is_none, base_hrs + ): + # Test that the precision radial velocity calibration must be "ThAr" for the + # high stability mode and None for all other modes. + hrs_data = base_hrs.model_dump() + del hrs_data["mode"] + if "prv_calibration" in hrs_data: + del hrs_data["prv_calibration"] + if prv_calibration_is_none: + with pytest.raises(ValueError): + Hrs(**hrs_data, mode=mode, prv_calibration="ThAr") + with nullcontext(): + Hrs(**hrs_data, mode=mode, prv_calibration=None) + else: + with nullcontext(): + Hrs(**hrs_data, mode=mode, prv_calibration="ThAr") + with pytest.raises(ValueError): + Hrs(**hrs_data, mode=mode, prv_calibration=None) From 94450852a212a2eebd4e589eaec0d6f2109a86b7 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 11 Nov 2025 11:51:57 +0200 Subject: [PATCH 056/171] Add a model for HRS detector setups --- src/aeonlib/salt/models/__init__.py | 3 ++- src/aeonlib/salt/models/hrs_models.py | 23 +++++++++++++++++++---- tests/salt/models/conftest.py | 12 ++++++++++-- tests/salt/models/test_hrs_models.py | 6 ++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 9e4f762..bafb35a 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,5 +1,5 @@ from .target_models import MagnitudeRange, SaltSiderealTarget -from .hrs_models import Hrs +from .hrs_models import Hrs, HrsDetector from .rss_models import ( Rss, RssDetector, @@ -26,6 +26,7 @@ "Block", "Constraints", "Hrs", + "HrsDetector", "SalticamDitherPattern", "SalticamFilterSequenceStep", "MagnitudeRange", diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py index 90b6266..6dcedcd 100644 --- a/src/aeonlib/salt/models/hrs_models.py +++ b/src/aeonlib/salt/models/hrs_models.py @@ -1,9 +1,11 @@ +from __future__ import annotations + from typing import Annotated, Self from astropy import units as u from pydantic import BaseModel, Field, PositiveInt, model_validator -from aeonlib.salt.models.types import HrsMode, HrsPrvCalibration +from aeonlib.salt.models.types import HrsMode, HrsPrvCalibration, PositiveDuration from aeonlib.salt.validators import GreaterEqual, LessEqual from aeonlib.types import Angle @@ -46,9 +48,9 @@ class Hrs(BaseModel): ) fibre_separation: Annotated[ Angle, GreaterEqual(16 * u.arcsec), LessEqual(63 * u.arcsec) - ] = 60 * u.arcsec - blue_arm: None - red_arm: None + ] = (60 * u.arcsec) + blue_arm: HrsDetector + red_arm: HrsDetector @model_validator(mode="after") def check_prv_calibration(self) -> Self: @@ -63,3 +65,16 @@ def check_prv_calibration(self) -> Self: f"prv_calibration must be None for the {self.mode} mode." ) return self + + +class HrsDetector(BaseModel): + """ + An HRS detector setup. + + A list of exposure times for the detector has to be specified. These exposure times + may be different for the blue and the red detector. + + Other detector properties, such as the readout speed or the binning, cannot be set. + """ + + exposure_times: list[PositiveDuration] diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index b40b01b..d6b2eb9 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -9,6 +9,7 @@ Block, Constraints, Hrs, + HrsDetector, MagnitudeRange, ReferenceStar, Request, @@ -196,5 +197,12 @@ def base_rss_dither_pattern(): @pytest.fixture() -def base_hrs(): - return Hrs(mode="medium resolution", blue_arm=None, red_arm=None) +def base_hrs(base_hrs_detector): + return Hrs( + mode="medium resolution", blue_arm=base_hrs_detector, red_arm=base_hrs_detector + ) + + +@pytest.fixture() +def base_hrs_detector(): + return HrsDetector(exposure_times=[50 * u.s, 45]) diff --git a/tests/salt/models/test_hrs_models.py b/tests/salt/models/test_hrs_models.py index f002771..6984dd0 100644 --- a/tests/salt/models/test_hrs_models.py +++ b/tests/salt/models/test_hrs_models.py @@ -63,3 +63,9 @@ def test_allowed_prv_calibration_depends_on_mode( Hrs(**hrs_data, mode=mode, prv_calibration="ThAr") with pytest.raises(ValueError): Hrs(**hrs_data, mode=mode, prv_calibration=None) + + +class TestHrsDetector: + def test_hrs_detector(self, base_hrs_detector): + # Test that HRS detector setups can be built. + assert True From 9316a95d269b5db150bb25d4278430767737b018 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 11 Nov 2025 11:54:55 +0200 Subject: [PATCH 057/171] Fix a docstring --- src/aeonlib/salt/models/rss_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index dcd65f4..43d1417 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -134,8 +134,8 @@ class RssSpectroscopy(BaseModel): Whether a nighttime flat should be taken for the observation. include_arc Whether a nighttime arc should be taken for the observation. - request_photometric_standard - Whether a photometric standard should be taken for the observation. + request_spectrophotometric_standard + Whether a spectrophotometric standard should be taken for the observation. """ From 4f7f744b5989cbf693f6ee08e6da9b4c2f23c9d2 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 11 Nov 2025 12:35:40 +0200 Subject: [PATCH 058/171] Fix formatting --- src/aeonlib/salt/models/hrs_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py index 6dcedcd..cd0e090 100644 --- a/src/aeonlib/salt/models/hrs_models.py +++ b/src/aeonlib/salt/models/hrs_models.py @@ -48,7 +48,7 @@ class Hrs(BaseModel): ) fibre_separation: Annotated[ Angle, GreaterEqual(16 * u.arcsec), LessEqual(63 * u.arcsec) - ] = (60 * u.arcsec) + ] = 60 * u.arcsec blue_arm: HrsDetector red_arm: HrsDetector From e8a64ebc077c49b1ad6469b1fc4967598a5bf280 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 11 Nov 2025 12:38:12 +0200 Subject: [PATCH 059/171] Add a model for NIRWALS configurations --- src/aeonlib/salt/models/__init__.py | 2 + src/aeonlib/salt/models/nirwals_models.py | 68 +++++++++++++++++++++++ src/aeonlib/salt/models/types/__init__.py | 16 ++++++ src/aeonlib/salt/models/types/nirwals.py | 31 +++++++++++ tests/salt/models/conftest.py | 13 +++++ tests/salt/models/test_nirwals_models.py | 38 +++++++++++++ 6 files changed, 168 insertions(+) create mode 100644 src/aeonlib/salt/models/nirwals_models.py create mode 100644 src/aeonlib/salt/models/types/nirwals.py create mode 100644 tests/salt/models/test_nirwals_models.py diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index bafb35a..596560e 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,5 +1,6 @@ from .target_models import MagnitudeRange, SaltSiderealTarget from .hrs_models import Hrs, HrsDetector +from .nirwals_models import Nirwals from .rss_models import ( Rss, RssDetector, @@ -27,6 +28,7 @@ "Constraints", "Hrs", "HrsDetector", + "Nirwals", "SalticamDitherPattern", "SalticamFilterSequenceStep", "MagnitudeRange", diff --git a/src/aeonlib/salt/models/nirwals_models.py b/src/aeonlib/salt/models/nirwals_models.py new file mode 100644 index 0000000..e1169d9 --- /dev/null +++ b/src/aeonlib/salt/models/nirwals_models.py @@ -0,0 +1,68 @@ +from typing import Annotated + +from astropy import units as u +from pydantic import BaseModel, PositiveInt, field_validator + +from aeonlib.salt.models.types import NirwalsCameraFilter, NirwalsFilter, NirwalsGrating +from aeonlib.salt.validators import GreaterEqual, LessEqual +from aeonlib.types import Angle + + +class Nirwals(BaseModel): + """ + A NIRWALS configuration. + + Every NIRWALS configuration includes a dither pattern. If you want to repeat this + pattern, you have to specify the number of cycles. + + Attributes + ---------- + num_cycles + The number of times the dither pattern should be done. + grating + The barcode of the grating to use. + grating_angle + The grating angle. This typically is half the articulation angle. + articulation_angle + The articulation angle. This must be a multiple of 0.5 degrees between 0 and 100 + degrees (both inclusive). + filter + The filter. + camera_filter + The camera filter. + dither pattern + The dither pattern. + include_arc + Whether a nighttime arc should be taken for the observation. + include_flat + Whether a nighttime flat should be taken for the observation. + request_spectrophotometric_standard + Whether a spectrophotometric standard should be taken for the observation. + + """ + + num_cycles: PositiveInt = 1 + grating: NirwalsGrating + grating_angle: Annotated[Angle, GreaterEqual(0 * u.deg), LessEqual(100 * u.deg)] + articulation_angle: Angle + filter: NirwalsFilter = "empty" + camera_filter: NirwalsCameraFilter + dither_pattern: None + include_arc: bool = True + include_flat: bool + request_spectrophotometric_standard: bool = False + + @field_validator("articulation_angle", mode="after") + @classmethod + def check_articulation_angle(cls, angle: Angle) -> Angle: + error = "The articulation angle must be a multiple of 0.5 degress between 0 and 100 degrees (both inclusive" + degrees = angle.to(u.deg).value + + if degrees < 0 or degrees > 100: + raise ValueError(error) + + n = 2 * degrees + if abs(n - round(n)) > 1e-6: + raise ValueError(error) + + return angle diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py index 752104b..1e9ff99 100644 --- a/src/aeonlib/salt/models/types/__init__.py +++ b/src/aeonlib/salt/models/types/__init__.py @@ -2,6 +2,15 @@ from .block import SkyTransparency from .duration import Duration, PositiveDuration from .hrs import HrsMode, HrsPrvCalibration +from .nirwals import ( + NirwalsCameraFilter, + NirwalsExposureType, + NirwalsFilter, + NirwalsGain, + NirwalsGrating, + NirwalsOffsetType, + NirwalsSampling, +) from .rss import ( RssGain, RssGrating, @@ -20,6 +29,13 @@ "HrsMode", "HrsPrvCalibration", "MagnitudeBandpass", + "NirwalsCameraFilter", + "NirwalsExposureType", + "NirwalsFilter", + "NirwalsGain", + "NirwalsGrating", + "NirwalsOffsetType", + "NirwalsSampling", "PositiveDuration", "RssGain", "RssGrating", diff --git a/src/aeonlib/salt/models/types/nirwals.py b/src/aeonlib/salt/models/types/nirwals.py new file mode 100644 index 0000000..03231ec --- /dev/null +++ b/src/aeonlib/salt/models/types/nirwals.py @@ -0,0 +1,31 @@ +from typing import Literal + + +NirwalsGrating = Literal["NG0950"] +"""A NIRWALS grating.""" + + +NirwalsFilter = Literal["empty"] +"""A NIRWALS filter.""" + + +NirwalsCameraFilter = Literal[ + "block", "clear", "cutoff 1.5um", "cutoff 1.7um", "diffuser" +] +"""A NIRWALS camera filter.""" + + +NirwalsOffsetType = Literal["FIF offset", "tracker guided offset"] +"""An offset type for NIRWALS.""" + + +NirwalsExposureType = Literal["science", "sky"] +"""An exposure type for NIRWALS.""" + + +NirwalsGain = Literal["faint"] +"""A gain option for NIRWALS.""" + + +NirwalsSampling = Literal["up-the-ramp"] +"""A sampling mode for NIRWALS.""" diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index d6b2eb9..fe0510a 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -27,6 +27,7 @@ RssLongslitSpectroscopy, RssMultiObjectSpectroscopy, RssSlitMaskIFUSpectroscopy, + Nirwals, ) @@ -206,3 +207,15 @@ def base_hrs(base_hrs_detector): @pytest.fixture() def base_hrs_detector(): return HrsDetector(exposure_times=[50 * u.s, 45]) + + +@pytest.fixture() +def base_nirwals(): + return Nirwals( + grating="NG0950", + grating_angle=25 * u.deg, + articulation_angle=50 * u.deg, + camera_filter="cutoff 1.5um", + dither_pattern=None, + include_flat=False, + ) diff --git a/tests/salt/models/test_nirwals_models.py b/tests/salt/models/test_nirwals_models.py new file mode 100644 index 0000000..85b7992 --- /dev/null +++ b/tests/salt/models/test_nirwals_models.py @@ -0,0 +1,38 @@ +from contextlib import nullcontext + +from astropy import units as u +import pytest + +from aeonlib.salt.models import Nirwals + + +class TestNirwals: + def test_nirwals(self, base_nirwals): + # Test that NIRWALS configurations can be built. + assert True + + @pytest.mark.parametrize( + "angle, expectation", + [ + (-1e-7 * u.deg, pytest.raises(ValueError)), + (0, nullcontext()), + (0.001, pytest.raises(ValueError)), + (12.499 * u.deg, pytest.raises(ValueError)), + (12.5, nullcontext()), + (12.501 * u.deg, pytest.raises(ValueError)), + ((74 * u.deg).to(u.rad), nullcontext()), + (100 * u.deg, nullcontext()), + ((100 + 1e-7) * u.deg, pytest.raises(ValueError)), + (100.5 * u.deg, pytest.raises(ValueError)), + (370 * u.deg, pytest.raises(ValueError)), + ], + ) + def test_articulation_angle_must_have_correct_value( + self, angle, expectation, base_nirwals + ): + # Test that the articulation anfle must be a multiple of 0.5 degrees between 0 + # and 100 degrees. + data = base_nirwals.model_dump() + data["articulation_angle"] = angle + with expectation: + Nirwals(**data) From 8f48434eb4de65d6f526110e3d87d2a4fbd78857 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 11 Nov 2025 14:17:19 +0200 Subject: [PATCH 060/171] Add a model for NIRWALS dither pattern steps --- src/aeonlib/salt/models/__init__.py | 3 +- src/aeonlib/salt/models/nirwals_models.py | 92 ++++++++++++++++++++++- tests/salt/models/conftest.py | 21 +++++- tests/salt/models/test_nirwals_models.py | 22 +++++- 4 files changed, 131 insertions(+), 7 deletions(-) diff --git a/src/aeonlib/salt/models/__init__.py b/src/aeonlib/salt/models/__init__.py index 596560e..5816a12 100644 --- a/src/aeonlib/salt/models/__init__.py +++ b/src/aeonlib/salt/models/__init__.py @@ -1,6 +1,6 @@ from .target_models import MagnitudeRange, SaltSiderealTarget from .hrs_models import Hrs, HrsDetector -from .nirwals_models import Nirwals +from .nirwals_models import Nirwals, NirwalsDitherPatternStep from .rss_models import ( Rss, RssDetector, @@ -29,6 +29,7 @@ "Hrs", "HrsDetector", "Nirwals", + "NirwalsDitherPatternStep", "SalticamDitherPattern", "SalticamFilterSequenceStep", "MagnitudeRange", diff --git a/src/aeonlib/salt/models/nirwals_models.py b/src/aeonlib/salt/models/nirwals_models.py index e1169d9..c1a38e5 100644 --- a/src/aeonlib/salt/models/nirwals_models.py +++ b/src/aeonlib/salt/models/nirwals_models.py @@ -1,9 +1,21 @@ -from typing import Annotated +from __future__ import annotations + +import math +from typing import Annotated, Literal from astropy import units as u from pydantic import BaseModel, PositiveInt, field_validator -from aeonlib.salt.models.types import NirwalsCameraFilter, NirwalsFilter, NirwalsGrating +from aeonlib.salt.models.types import ( + NirwalsCameraFilter, + NirwalsExposureType, + NirwalsFilter, + NirwalsGain, + NirwalsGrating, + NirwalsOffsetType, + NirwalsSampling, + PositiveDuration, +) from aeonlib.salt.validators import GreaterEqual, LessEqual from aeonlib.types import Angle @@ -47,7 +59,7 @@ class Nirwals(BaseModel): articulation_angle: Angle filter: NirwalsFilter = "empty" camera_filter: NirwalsCameraFilter - dither_pattern: None + dither_pattern: list[NirwalsDitherPatternStep] include_arc: bool = True include_flat: bool request_spectrophotometric_standard: bool = False @@ -66,3 +78,77 @@ def check_articulation_angle(cls, angle: Angle) -> Angle: raise ValueError(error) return angle + + +class NirwalsDitherPatternStep(BaseModel): + """ + A step in a NIRWALS dither pattern. + + Each step os characterised by the offset type and the offsets in horizontal and + vertical direction, the exposure type and time, and other detector-related + properties. The offset directions are the on-telescope directions (i.e. with the + field rotated by the position angle) + + If a reference star is provided for the acquisition, for the first step the + offset type must be "tracker guided offset" and the offsets must be equal to + those from the reference star to the target. + + Parameters + ---------- + offset_type + The offset type. + horizontal offset + The offset in horizontal telescope direction. This must be between -100 and 100 + arcseconds (both inclusive). + vertical_offset + The offset in vertical telescope direction. This must be between -100 and 100 + arcseconds (both inclusive). + exposure_type + The exposure type. + exposure_time + The exposure time. + gain + The gain to use. + sampling + The sampling method to use. + num_reads + The number of detector readouts. This must be 1. + num_ramps + The number of ramps. This must be 1. + """ + + offset_type: NirwalsOffsetType + horizontal_offset: Annotated[ + Angle, GreaterEqual(-100 * u.arcsec), LessEqual(100 * u.arcsec) + ] + vertical_offset: Annotated[ + Angle, GreaterEqual(-100 * u.arcsec), LessEqual(100 * u.arcsec) + ] + exposure_type: NirwalsExposureType + exposure_time: PositiveDuration + gain: NirwalsGain + sampling: NirwalsSampling + num_reads: Literal[1] = 1 + num_ramps: Literal[1] = 1 + + @property + def num_groups(self): + """ + The number of groups. + + The number of groups is equal to the ratio of the exposure time and the product + of reads and frame rate. A value of 0.728 seconds is assumed for the frame rate. + + Returns + ------- + The number of groups. + """ + frame_rate = 0.728 * u.s + # The actual frame rate value is 0.727750 s. However, as a safety measure and to + # avoid rounding differences between different pieces of software, a rounded + # value is used. + + groups = round( + math.floor(float(self.exposure_time / (self.num_reads * frame_rate))) + ) + return max(1, groups) diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index fe0510a..19c1d7e 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -28,6 +28,7 @@ RssMultiObjectSpectroscopy, RssSlitMaskIFUSpectroscopy, Nirwals, + NirwalsDitherPatternStep, ) @@ -210,12 +211,28 @@ def base_hrs_detector(): @pytest.fixture() -def base_nirwals(): +def base_nirwals(base_nirwals_dither_pattern_step): return Nirwals( grating="NG0950", grating_angle=25 * u.deg, articulation_angle=50 * u.deg, camera_filter="cutoff 1.5um", - dither_pattern=None, + dither_pattern=[ + base_nirwals_dither_pattern_step, + base_nirwals_dither_pattern_step, + ], include_flat=False, ) + + +@pytest.fixture() +def base_nirwals_dither_pattern_step(): + return NirwalsDitherPatternStep( + offset_type="FIF offset", + horizontal_offset=-20 * u.arcsec, + vertical_offset=35 * u.arcsec, + exposure_type="science", + exposure_time=200 * u.s, + gain="faint", + sampling="up-the-ramp", + ) diff --git a/tests/salt/models/test_nirwals_models.py b/tests/salt/models/test_nirwals_models.py index 85b7992..818257f 100644 --- a/tests/salt/models/test_nirwals_models.py +++ b/tests/salt/models/test_nirwals_models.py @@ -3,7 +3,7 @@ from astropy import units as u import pytest -from aeonlib.salt.models import Nirwals +from aeonlib.salt.models import Nirwals, NirwalsDitherPatternStep class TestNirwals: @@ -36,3 +36,23 @@ def test_articulation_angle_must_have_correct_value( data["articulation_angle"] = angle with expectation: Nirwals(**data) + + +class TestNirwalsDitherPatternStep: + def test_nirwals_dither_pattern_step(self): + # Test that NIRWALS dither pattern step setups can be built. + assert True + + @pytest.mark.parametrize( + "exposure_time, num_groups", + [(0.001 * u.s, 1), (37 * u.s, 50), (70_000 * u.ms, 96)], + ) + def test_number_of_groups( + self, exposure_time, num_groups, base_nirwals_dither_pattern_step + ): + # Test that the number of groups is calculated correctly. + data = base_nirwals_dither_pattern_step.model_dump() + data["exposure_time"] = exposure_time + step = NirwalsDitherPatternStep(**data) + + assert step.num_groups == num_groups From 36dbf11a7bf1a6f75a0a77d98b41d8f6f2fe0d92 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 11 Nov 2025 14:21:01 +0200 Subject: [PATCH 061/171] Fix docstrings --- src/aeonlib/salt/models/block_models.py | 6 +++--- src/aeonlib/salt/models/hrs_models.py | 4 ++-- src/aeonlib/salt/models/nirwals_models.py | 2 +- src/aeonlib/salt/models/request_models.py | 2 +- src/aeonlib/salt/models/rss_models.py | 16 ++++++++-------- src/aeonlib/salt/models/salticam_models.py | 6 +++--- src/aeonlib/salt/models/target_models.py | 4 ++-- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index b86a173..d99277e 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -61,7 +61,7 @@ class Block(BaseModel): is transferred to Cape Town (before the pipeline runs), you can set the data notification accordingly. - Attributes + Parameters ---------- name Human-friendly name for the block. This must be unique within a proposal. @@ -144,7 +144,7 @@ class Constraints(BaseModel): The seeing must be given for the zenith. - Attributes + Parameters ---------- transparency Required sky transparency. @@ -180,7 +180,7 @@ class Acquisition(BaseModel): include additional finder charts, for example if your target is a transient and hence will not show on the automatically generate finder charts. - Attributes + Parameters ---------- finder_charts Additional finder charts. The specified files must exist. diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py index cd0e090..3b66ef9 100644 --- a/src/aeonlib/salt/models/hrs_models.py +++ b/src/aeonlib/salt/models/hrs_models.py @@ -21,7 +21,7 @@ class Hrs(BaseModel): A sequence of exposure times can be defined for the red and blue detector arm. If this sequence shall be executed more than once, a number of cycles need to be set. - Attributes + Parameters ---------- num_cycles How often the exposure time patterns shall be executed. @@ -48,7 +48,7 @@ class Hrs(BaseModel): ) fibre_separation: Annotated[ Angle, GreaterEqual(16 * u.arcsec), LessEqual(63 * u.arcsec) - ] = 60 * u.arcsec + ] = (60 * u.arcsec) blue_arm: HrsDetector red_arm: HrsDetector diff --git a/src/aeonlib/salt/models/nirwals_models.py b/src/aeonlib/salt/models/nirwals_models.py index c1a38e5..38b5fa0 100644 --- a/src/aeonlib/salt/models/nirwals_models.py +++ b/src/aeonlib/salt/models/nirwals_models.py @@ -27,7 +27,7 @@ class Nirwals(BaseModel): Every NIRWALS configuration includes a dither pattern. If you want to repeat this pattern, you have to specify the number of cycles. - Attributes + Parameters ---------- num_cycles The number of times the dither pattern should be done. diff --git a/src/aeonlib/salt/models/request_models.py b/src/aeonlib/salt/models/request_models.py index 863ee57..a3131cd 100644 --- a/src/aeonlib/salt/models/request_models.py +++ b/src/aeonlib/salt/models/request_models.py @@ -10,7 +10,7 @@ class Request(BaseModel): """ An observation request for SALT. - Attributes + Parameters ---------- proposal_code Unique identifier of the proposal for which this request is submitted. diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 43d1417..f7dc95c 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -60,7 +60,7 @@ class Rss(BaseModel): You may define a dither pattern, in which case the wave plate sequence (with its cycles and exposures) applies to each dither pattern step. - Attributes + Parameters ---------- num_cycles How often to cycle through the wave plate sequence. This is only relevant if @@ -88,7 +88,7 @@ class RssImaging(BaseModel): """ An RSS imaging configuration. - Attributes + Parameters ---------- filter The filter to use. This may be one of RSS's own imaging filters or one of the @@ -117,7 +117,7 @@ class RssSpectroscopy(BaseModel): While the grating, articulation, polarimetry and calibrations are defined by this class, the slit mask (or IFU) to use is specified in a child class. - Attributes + Parameters ---------- grating The barcode of the grating, such as "pg0900". @@ -178,7 +178,7 @@ class RssLongslitSpectroscopy(RssSpectroscopy): In addition to the properties required by a generic RSS spectroscopy the user must specify the barcode of the longslit to use. - Attributes + Parameters ---------- slit The barcode of the longslit, such as "PL0125N001". @@ -195,7 +195,7 @@ class RssMultiObjectSpectroscopy(RssSpectroscopy): specify the path of the file describing the MOS mask. The path must exist and must be a file. - Attributes + Parameters ---------- mask The file path of the file describing the MOS mask. @@ -211,7 +211,7 @@ class RssSlitMaskIFUSpectroscopy(RssSpectroscopy): In addition to the properties required by a generic RSS spectroscopy the user must specify the barcode of the slit mask IFU to use. - Attributes + Parameters ---------- slit_mask_ifu The barcode of the slit mask IFU, such as "PF0200N001". @@ -301,7 +301,7 @@ class RssDetector(BaseModel): """ An Rss detector setup. - Attributes + Parameters ---------- exposure_time The exposure time. If multiple exposures are requested, this is the time per @@ -350,7 +350,7 @@ class RssDitherPattern(BaseModel): Therefore, if a particular object orientation is desired, a suitable position angle must be chosen so that the dithers coincide with the detector axes. - Attributes + Parameters ---------- num_rows Number of rows in the pattern. diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index 37ba71c..31a0531 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -35,7 +35,7 @@ class Salticam(BaseModel): You may define a dither pattern, in which case the filter sequence (with its cycles and exposures) applies to each dither pattern step. - Attributes + Parameters ---------- num_cycles How often to cycle through the filter sequence. @@ -60,7 +60,7 @@ class SalticamFilterSequenceStep(BaseModel): """ A step in a filter sequence. - Attributes + Parameters ---------- filter Filter for the step. @@ -107,7 +107,7 @@ class SalticamDitherPattern(BaseModel): Therefore, if a particular object orientation is desired, a suitable position angle must be chosen so that the dithers coincide with the detector axes. - Attributes + Parameters ---------- num_rows Number of rows in the pattern. diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py index 3863a47..2b4c3de 100644 --- a/src/aeonlib/salt/models/target_models.py +++ b/src/aeonlib/salt/models/target_models.py @@ -19,7 +19,7 @@ class SaltSiderealTarget(SiderealTarget): This model extends the `SiderealTarget` model by adding a target type and a magnitude range. - Attributes + Parameters ---------- target_type Target type. This must be the label for SIMBAD object type (see @@ -45,7 +45,7 @@ class MagnitudeRange(BaseModel): The minimum (brightest) and maximum (faintest) magnitude must be give for a particular bandpass filter. - Attributes + Parameters ---------- min_magnitude Minimum (brightest) magnitude. From 47b19ca609bd0a531191a960b1fce40bcb3be1b2 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 2 Feb 2026 14:50:01 +0200 Subject: [PATCH 062/171] Add functionality to validate XML against the SALT schema --- pyproject.toml | 3 + src/aeonlib/salt/models/serialize/__init__.py | 0 .../salt/models/serialize/proposal.xsd | 5044 +++++++++++++++++ src/aeonlib/salt/models/serialize/util.py | 41 + tests/salt/models/test_util.py | 37 + uv.lock | 6 +- 6 files changed, 5130 insertions(+), 1 deletion(-) create mode 100644 src/aeonlib/salt/models/serialize/__init__.py create mode 100644 src/aeonlib/salt/models/serialize/proposal.xsd create mode 100644 src/aeonlib/salt/models/serialize/util.py create mode 100644 tests/salt/models/test_util.py diff --git a/pyproject.toml b/pyproject.toml index fbde488..ae6b193 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,9 @@ lt = [ "lxml-stubs>=0.5.1", "suds>=1.2.0", ] +salt = [ + "lxml>=6.0.1", +] [tool.pytest.ini_options] addopts = ["--import-mode=importlib", "-m not online"] diff --git a/src/aeonlib/salt/models/serialize/__init__.py b/src/aeonlib/salt/models/serialize/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/aeonlib/salt/models/serialize/proposal.xsd b/src/aeonlib/salt/models/serialize/proposal.xsd new file mode 100644 index 0000000..c051f22 --- /dev/null +++ b/src/aeonlib/salt/models/serialize/proposal.xsd @@ -0,0 +1,5044 @@ + + + + + + + + The root node of a SALT proposal. + + + + + + The title of the proposal. + + + + + A text based version of the abstract. + + + + + + + + The semester. + + 1: 1 May to 31 October + 2: 1 November to 30 April + + + + + + + + + + + 1 = Request for time. + + + + + + + + + The role played by the South African investigators. + + + + + + + + + + + For targets of opportunity. Act on alert authorisation. + + + + + + + Flag indicating whether the proposal contains a time critical + observation. + + + + + + + Flag indicating whether the proposal is a Priority 4 proposal. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The time request for a semester. + + + + + + + + + The semester. + + 1: 1 May to 31 October + 2: 1 November to 30 April + + + + + + + + + + + + + + + + + + + Basic details required for a DDT, commissioning or engineering proposal. + + + + + + The title of the proposal. + + + + + + + The total requested time for this proposal. + + + + + + + + + + + + + + The root node of a SALT proposal. + + + + + + + + + + 2 = Detailed telescope configuration. + + + + + + + + + + The period for which the data remains proprietary to the investigators. + + + + + + + + + + + + + + + + + + + + + + + + Several elements (namely SubBlock, SubSubBlock, Pointing, TelescopeConfig and PayloadConfig + elements may or may not be shown in a GUI representation of the proposal. The + RequiredElements element states which of these elements have to be shown as they are + syntactically required. The RequestedElements element, on the other hand, states which + elements should be shown irrespective of whether they are necessary from a syntactic point + of view. + + + + + + + + + + + + + + + + + + + + + The semester-specific proposal details. + + + + + + + The title of the proposal. + + + + + A text based version of the abstract. + + + + + + + + A short phrase describing the science for the nightlog summary. Examples would be "Study of the + properties of thin and thick disks of galaxy IC2531" or "Tracing the history of LIRGs". + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The semester. + + 1: 1 May to 31 October + 2: 1 November to 30 April + + + + + + + + + + + + For targets of opportunity. Act on alert authorisation. + + + + + + + + + + + A payload configuration is mainly used to cope with the situation where a + pellicle setup is being used. It is a rather simple matter to cope with a single instrument + being used at a time. The complexity comes in when two instruments are used concurrently. + With the pellicle setup the primary instrument is always SALTICAM. The fold mirrors allow + one other payload port to be used. So we can use SALTICAM and RSS or SALTICAM and FIF + (Fibre Instrument Feed) or SALTICAM and the auxilliary port. + + + + + + + + + + + If true for a pellicle setup, the next step starts only after both instruments + have finished with the current configuration. In case of no pellicle setup the + value of this element is irrelevant. + + + + + + + + + + + + + + + + + + + + + + + + + The type of this payload configuration. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main purpose of the TelescopeConfig element is to group PayloadConfig elements + together. + + + + + + + + Position angle of the camera. Degrees from north (being zero) clockwise + for positive angle + + + + + + + + + + A fixed position angle must not be flipped by 180 degrees. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A time restriction for an observation. + + + + + + + + + + + + A phase constraint cannot be defined without first defining an Ephemeris in + the referenced Target. + + + + + + + + + + + + + + + + + + + + + + + + + + Describes what seeing conditions are acceptable (in arcsecs). + + + + + + + + + + + + + An acquisition for an observation, including a target, an acquisition telescope + configuration and, optionally, a reference star on which to acquire. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The reference star to use for an acquisition. + + + + + + + + + + + + An acquisition and a list of telescope configurations. + + + + + + + + + + + + + + + All the information required for a single track observation. In general, this will be a + single Observation. However, if several targets in the same field of view are considered (e.g., three + stars in a cluster), more than one Observation might be included. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Whether this block is charged for. + + + + + + + + + The minimum angular distance between the Moon and the target. + + + + + + + + + + + + + + + + + + + + + The maximum number of visits for all semesters together. + + + + + + + Treat the visits as a single continuous visits, i.e. do them all consecutively without any + wait time between them. + + + + + + + + + + + + + + + + + + + + The block type, such as "Science". + + + + + + + + + + + + + Specifies the priority of the time when this block should + be observed. + + + + + + + + + + + + + The number of days to wait between each visit to this block. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration information required for the data reduction pipeline. + + + + + + + + + + + + + Allows the PI to choose when to be notified that an observation has been done. + The following options exist: + 1) Normal: The PI is notified by email when the raw and reduced data from a night is ready + for retrieval from the ftp server. + 2) Fast: After a night of observations, the individual raw data files are immediately made + available for download and the PI is notified by email that the data is available. + 3) User: he user will download the data from the web manager. The PI will be notified when + new raw and reduced data is accessible via the web manager. + 4) Complete: The PI will be notified when the observations for the proposal are complete and + all data is available for download. + + + + + + + + + + + + + + + The format for the reduction documentation. + + + + + + + + + + + + + A pool of Blocks. + + + + + + + + + + + + + + + + + + + + + + + + The pool type, such as "Science". + + + + + + + + + + + + A pool rule. + + + + + + + + + + + + The available pool rule functions. + + + + + + + + + + + + + Relevant information which was provided in the Phase 1 + proposal. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The time allocated by the TACs for the various priorities. + + + + + + + + + + + + + + A time transfer between observing conditions. + + + + + + + + + + + + + + + + + + + + + + + + + + + + An observation performed already for a block. + + + + + + + + + + + + + + + + + + The status of a block visit. + + + + + + + + + + + + + + + A server assigned unique id and the corresponding unique PIPT assigned id. + + + + + + + + + + + + + + Records which elements are used. + + + + + + + + + + + + + + + + + + The type of proposal (science, commissioning or director's discretionary time). + + + + + + + + + + + + + + + + + + + + + + + The investigators associated with this proposal. + + + + + + + + + + + + + + + + + + + + + + + + + The SALT partners associated with this proposal. + + + + + + + + + + + + + The percentage of the total required time to be charged to this SALT + partner. + + + + + + + + + + The semester. + + 1: 1 May to 31 October + 2: 1 November to 30 April + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the SALT partner. + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the institute. + + + + + + + + + + + Indicates that the PI for the proposal is affiliated to an + African institution (outside South Africa). + + + + + + + + + + + + The file path to the (external) PDF version of the scientific justification. + + + + + + + + + + + + A thesis which this proposal will form part of. + + + + + + + The type of degree awarded for the thesis. + + + + + + + The expected year of completion for the thesis. + + + + + + + + + + + + + A brief description of the importance and contribution to the thesis as well as the implication + if the data are not obtained in time. + + + + + + + + + + + The type of degree awarded for a thesis which this proposal + forms part of. + + + + + + + + + + + + + This element contains the source of funding, if the proposal + is made possible by external support. + + + + + + + + + + + + The status of a previous proposal. + + + + + + + + + + + + SALT-related refereed publications based on the previous proposal, as a list of ADS bibcodes. + + + + + + + + + + + The list of targets. + + + + + + + + The number of optional targets requested to be observed. + + + + + + + + + + + + + + A unique string identifying the target. + + + + + + + + + + + + + + + + + + + + + + + Flag indicating whether the target is mandatory (true) + or optional (false). + + + + + + + + + + + + A magnitude range. + + + + + + + + + + + + + + + + + + + + + + + + + + The bandpass for the magnitude range. + + + + + + + + + + + + + + + + + + A time when the period begins. The available time bases are + + BJD: Baryocentric Julian Date + HJD: Heliocentric Julian Date + JD: Julian Date + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The available time base values for the TimeZero element + of a periodic target ephemeris. These are + + BJD: Baryocentric Julian date + HJD: Heliocentric Julian date + JD: Julian date + + + + + + + + + + + + + + + A finding chart. + + The finding chart can be specified as a path to an external file, as an image server to use for creating + it automatically, as a URL to it, or by giving its MIME type and its content as a Base64 encoded string. + + + + + + + + + + + + + + + + + + + + + Sky transparency. + + + + + + + + + + + + + + + The maximum lunar phase allowed for this block. + + + + + + + + + + + + + + + + + + + + Sky brightness due to the Moon. + + Dark: Phase angle > 135 degrees, or Moon set + Dark-Gray: Phase angle between 90 and 135 degrees + Bright-Gray: Phase angle between 45 and 90 degrees + Bright: Phase angle between 0 and 45 degrees + Any: Any phase angle + + + + + + + + + + + + + + A seeing value. + + + + + + + + + + + + + + + + + + + The ranking of an observation (for Phase 1) or of a block within + the priority (for Phase 2). + + + + + + + + + + + + + + An instrument simulation obtained with an instrument simulator. + + + + + + + + + + + + + + Details related to public outreach. These include a summary for the general + public as well as a flag indicating whether the summary of the proposal may + be displayed along with the all-sky camera image. + + + + + + + + + + + + + + + + + + + + + A SALTICAM instrument configuration + + + + + + + + + + A Salticam filter. As a list of simple types causes problems for the PIPT, + it is defined as a complex type with a Filter child element. + + + + + + + + + + + + + + + + + + + + + A SALTICAM instrument configuration + + + + + + + + + Minimum signal-to-noise required. + + + + + + + + + + + + + A Salticam Detector setup. + + + + + + Combine this many CCD rows during readout + + + + + + + + + + + Combine this many CCD columns during readout + + + + + + + + + + + + + + + Defines up to 42 CCD windows, used to restrict readout rows and columns +
    +
  • CentreRA - Right ascension for the window centre
  • +
  • CentreDec - Declination of the window centre
  • +
  • Width - Width of the window in arcseconds
  • +
  • NRows - Height of the window in arcseconds
  • +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + Type of exposure to be made +
    +
  • Science - Science exposure
  • +
  • Bias - Bias exposure.
  • +
  • Flat Field - Flat field exposure.
  • +
+
+
+ + + + + +
+ + + + + This sets the amplifier gain for the CCD readout amplifiers. +
    +
  • Faint - high gain for faint objects.
  • +
  • Bright - low gain for bright objects.
  • +
+
+
+ + + + +
+ + + + + Sets the readout speed. Faster readouts have more noise. +
    +
  • None - don't do a readout.
  • +
  • Slow - do a low noise, slow readout.
  • +
  • Fast - do a higher noise, faster readout.
  • +
+
+
+ + + + + +
+ + + + + This specifies the way the instrument is operated. + + + + + + + + + + An array of SALTICAM filters to be used in sequence as listed with + associated exposure times. + + + + + + + + + + + + + A Salticam calibration defined within a Salticam configuration. + + + + + + + + + + + + + + + + + + + + + The number of cycles between two flats. This element should only be used if + "Every N Cycles" is chosen as the flat requirement. + + + + + + + + + + + + + + + + + + + + + + + + + + + Calibration flat lamps. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A Salticam calibration setup defined outside a Salticam setup. + + + + + + + + + + + + + Describes the readout modes + + + + + + + + + + + + + + + An RSS instrument configuration + + + + + + + + + + + + + + + + + + + + The observing mode. + + + + + + + + + + + + An imaging setup. + + + + + + + A spectroscopy setup. + + + + + + + + + + + Different options are available for the slitmask: + - Longslit: a slit from a predefined list of longslits + - MOS (Multi object spectroscopy): a slit mask id and a PSMF file + + + + + + + + + + + + + + + + + + + + A predefined slit mask, such as a longslit. + + + + + + + + + + + + The type of a predefined slit mask. + + + + + + + + + + + + + + + + + + A Fabry-Perot setup. + + + + + + + + + + + + The polarimetry types. + + + + + + + + + + + + + + + + + + + + + + This describes the mechanical configuration of the instrument + + + + + + This specifies the exposure for the detector controller + + + + + + Minimum signal-to-noise required. + + + + + + + + + + This states whether the RSS setup is a (nighttime) calibration. + + + + + + + + + + + An RSS procedure. + + + + + + + + + + + + + An RSS configuration. + + + + + Slitmask info. + + + + + + + + + + + The focus position in microns. This is referred to the home indicator + of the focus actuator in the camera. + + + + + + + + + + + + + + + + + + + + + + + + The mode for this setup and the corresponding instrument configuration such as (in case of polarimetry) + the waveplate pattern. + + + + + + + + + + + + + + An RSS imaging configuration. + + + + + + + + + + + + + + + + + + + + An RSS Fabry-Perot configuration. + + + + + + + + + + + + An RSS polarimetry configuration. + + + + + + + + + + + + The orientation of the beamsplitter, which can be either normal or parallel. + + + + + + + + + + + + + Different options are available for the slitmask: + - Longslit: a slit from a predefined list of longslits + - MOS (Multi object spectroscopy): a slit mask id and a PSMF file + - SMI (Slit Mask IFU): a slit mask IFU from a predefined list + + + + + + + + + + + + + + + + + + + + + + + + + + + A predefined slit mask, such as a longslit. + + + + + + + + + + + + A slit mask IFU. + + + + + + + + + + + + A list of half and/or quarter waveplate stations. + + + + + + + + + + + + + + + + + + + A calibration region for Fabry-Perot. + + + + + + + + + + + + An RSS Detector setup. + + + + + + + + + CCD windowing as implemented by the RSS detector, requires a + height (FrameSize) for a window spanning the entire length of the CCD. + + + + + + + + + + + + + + + + + + + + + + + Shuffles this many CCD rows before an exposure + + + + + + + + + + Shuffles this many CCD rows after an exposure + + + + + + + + + + Combine this many CCD rows during readout + + + + + + + + + + + Combine this many CCD columns during readout + + + + + + + + + + + States whether or not the CCD will be prepared for the next exposure + after readout - necessary for all CCD modes apart from shuffle. + + + + + + + + + + + + + + + The RSS detector can do various calculations on an image. +
    +
  • None - no calculation.
  • +
  • Aperture - compute the location of backlit slitmask apertures.
  • +
  • Star - compute the location of stars.
  • +
  • Peakup - centre stars onto slitmask apertures.
  • +
+
+
+ + + + + + + + + +
+ + + + + Type of exposure to be made +
    +
  • Science - Science exposure.
  • +
  • Bias - Bias exposure.
  • +
  • Flat Field - Flat field exposure.
  • +
  • Arc - Wavelength calibration exposure using arc lamp.
  • +
  • Dark
  • +
+
+
+ + + + + + + +
+ + + + + This sets the amplifier gain for the CCD readout amplifiers. +
    +
  • Faint - high gain for faint objects.
  • +
  • Bright - low gain for bright objects.
  • +
+
+
+ + + + +
+ + + + + The readout speed. Faster readouts have more noise. +
    +
  • None - don't do a readout.
  • +
  • Slow - do a low noise, slow readout.
  • +
  • Fast - do a higher noise, faster readout.
  • +
+
+
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An RSS calibration defined within an RSS setup. + + + + + + + + + + + + + + + + + + + + + + + The number of cycles between two arcs. This element should only be used if + "Every N Cycles" is chosen as the arc requirement. + + + + + + + + + + + + Indicates whether this calibration shall be carried out between dithering + steps. If the arc requirement the value of this element is ignored, as in + this case the calibration needs to be done between dithering steps anyway. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The number of cycles between two flats. This element should only be used if + "Every N Cycles" is chosen as the flat requirement. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Arc lamps. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Calibration flat lamps. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An RSS calibration setup defined outside an RSS setup. + + + + + + + + + + + + + + A barcode for a predefined slit mask, such as a longslit or a slit mask IFU. + + + + + + + + + + + + The bar codes of the available RSS filters. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The bar code of the desired grating. + + + + + + + + + + + + + + + + + The grating can be rotated to any angle between 0 and 100 degrees. The + angular choice is quantized only by the resolution of the stepper motor. + + + + + + + + + + + + + + + + + + + There are 132 detented locations. The enumerated values consists of two + integers. The first integer represents the station number. The second gives the camera + angle in hundredths of a degree. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The available configurations are TF (tunable filter), LR (low resolution), + MR (medium resolution) and HR (high resolution). + + + + + + + + + + + + + + + + A list of wavelength ranges or wavelengths for etalon positions. + + + + + + + + + + + + + + + + + + + + + + + + + + Describes the readout modes + + + + + + + + + + + + + + + + + An HRS setup. + + + + + + + + + + + + + + + An HRS setup. + + + + + + + + + + + + + + + + An HRS detector setup. + + + + + + + + + The number of amplifiers to use when reading out the CCD. + + + + + + + + + + + Combine this many CCD rows during readout + + + + + + + + + + + Combine this many CCD columns during readout + + + + + + + + + + + + + + + + + The readout speed. Faster readouts have more noise. +
    +
  • None - don't do a readout.
  • +
  • Slow - do a low noise, slow readout.
  • +
  • Fast - do a higher noise, faster readout.
  • +
+
+
+ + + + + +
+ + + + + + Configuration of the HRS. + + + + + + + + + + + + + Whether an exposure with the ThAr lamp should be done in addition to the one + with the iodine cell. This is only relevant if the iodine cell is used , i.e. + if the value of the IodineCellPosition element is IN. + + + + + + + + + + + Exposure type. + + + + + + + + + + + + + + + + A nod and shuffle setting. + + + + + + + The time per nod interval. + + + + + + + + + + + + + + + + + + + The number of nods required. + + + + + + + + + + + Iodine cell position. + + + + + + + + + + + + + + + Target location. + + + + + + + + + + + + Fibre separation in arcseconds + + + + + + + + + + + + + + + + + + + + A procedure. + + + + + + + + + + + + + + An exposure pattern. + + + + + + + + + + + An HRS calibration. + + + + + + + An HRS calibration setup defined outside an HRS setup. + + + + + + + + + + + + + + HRS mode. + + + + + + + + + + + + + + + + + + NIR setup. + + + + + + + + + + + + + + + + + + + + + + This states whether the NIRWALS setup is a (nighttime) calibration. + + + + + + + + + + NIR sampling options. + + + + + + + + + + + + + Gain setting. + + + + + + + + + + + + + NIR detector setup. + + + + + + + + + + How many reads are done for each sample. For Fowler this is how many reads are done at the beginning and at the end. For Up-the-Ramp Group this is how many reads you do at each sample (group) up the ramp. + + + + + + + + + + The number of samples up the ramp for Up-the-Ramp Group mode. + + + + + + + + + + The number of samples up the ramp for Up-the-Ramp Group mode. + + + + + + + + + + + + + + + The grating can be rotated to any angle between 0 and 50 degrees. + + + + + + + + + + + + + + + + + + + + Articulation station. Each value has two numbers. The first represents + the station number. The second gives the camera angle in degrees. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Barcode of the NIR filter. + + + + + + + + + + + + NIR camera filter wheel. + + + + + + + + + + + + + + + + Bundle separation in arcseconds. + + + + + + + + + + + + + NIR configuration. + + + + + + + + + + + + + + + + + Dither offset, in image coordinates. + + + + + + + + Horizontal offset (in image coordinates) from the first + position, in milliarcseconds. Must be 0 for the first + dither step. + + + + + + + + + + + + Vertical offset (in image coordinates) from the first + position, in milliarcseconds. Must be 0 for the first + dither step. + + + + + + + + + + + + + + + Dither offset type. + + + + + + + + + + + + + + + Exposure type. + + + + + + + + + + + + + + + + + Step in a dither pattern. + + + + + + + + + + + + + + + A dither pattern. + + + + + + + + + + + + NIR procedure type. + + + + + + + + + + + + + NIR procedure. + + + + + + + + + + + + + + + + + + + + + + + + Arc lamps. + + + + + + + + + + + + + + + + + + NIR arc setup. + + + + + + + + + + + + + + + + + + + + The number of cycles between two arcs. This element should only be used if + "Every N Cycles" is chosen as the arc requirement. + + + + + + + + + + + + Indicates whether this calibration shall be carried out between dithering + steps. If the arc requirement the value of this element is ignored, as in + this case the calibration needs to be done between dithering steps anyway. + + + + + + + + + + + + + + + + + + + + + + Calibration flat lamps. + + + + + + + + + + + + + + Calibration flat setup. + + + + + + + + + + The number of cycles between two flats. This element should only be used if + "Every N Cycles" is chosen as the flat requirement. + + + + + + + + + + + + + + + + An NIR calibration. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NIR grating. + + + + + + + + + + + + + + A BVIT configuration. + + + + + + + + + + + + + + + A BVIT setup. + + + + + + + + + + + + + + + + + + + The operational mode. In imaging mode, a normal exposure is made. + In streaming mode, each incoming photon is tagged with the arrival + time. + + + + + + + + + + + + + The value x in the transmission factor 10^-x of the neutral density filter. + For example, the value 0.3 means that the ratio I/I0 of the intensities I + and I0 after and before passing the filter is I/I0=10^-0.3. The value "open" + indicates that no neutral density filter is used. + + + + + + + + + + + + + + + + The iris size to use for the BVIT observation. + + + + + + + + + + + + A BVIT calibration. + + + + + + + A BVIT calibration setup defined outside a BVIT setup. + + + + + + + + + + + + + + A BVIT filter. The value "Open" indicates that no filter is used. + + + + + + + + + + + + + + + + + + The available target types. Most of the types listed here are + standard names taken from the Simbad target classification + (http://simbad.u-strasbg.fr/simbad/sim-display?data=otypes). + See the SALT document on target classification for a detailed + explanation. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The coordinates of a target. + + + + + + + + + + + + + + + An angle. A value for this type must start with a digit, optionally + preceded by a plus or minus sign. + + + + + + + + + + + + A proper motion of a target. + + + + + + + + + + + + + + A path to a table containing the target coordinates. + + + + + + + + + + + + + Identifier and output interval to use for obtaining the + ephemerides from NASA JPL's Horizons service. + + The identifier may either be an object name (such as "Ubuntu") + or a designation (such as "2005 EW302"). + + + + + + + + + + + + + + + + + + + + + + + + + + + + Mode of tracking (sidereal or non-sidereal). + + + + + + + + + + + + Equatorial Right Ascension of <Target>, measured in hours, + minutes, and seconds. An optional proper motion may be included. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Container for the equatorial celestial coordinate of a Target in degrees, + arcminutes and (decimal) arcseconds. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Exposure time in seconds + + + + + + + + + + + + + + + + + Exposure time in seconds for a semester. + + + + + + The semester (relative to the semester for which the proposal is first + submitted). So if the proposal spans three semesters, the value may bw + 1, 2 or 3. + + + + + + + + + + + + + + + + + + + + + + + A number of iterations, i.e. a positive integer (which mustn't exceed the + upper limit of xs:unsignedInt). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Dithering, characterized by the number of horizontal and vertical tiles, the number of + dithering steps and the offset between steps (which is the same in both x and y direction). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The year and semester for which a block is intended. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/aeonlib/salt/models/serialize/util.py b/src/aeonlib/salt/models/serialize/util.py new file mode 100644 index 0000000..5968898 --- /dev/null +++ b/src/aeonlib/salt/models/serialize/util.py @@ -0,0 +1,41 @@ +import io +import pathlib + +from lxml import etree + +_schema: etree.XMLSchema | None = None + + +def validate_xml(xml: str) -> None: + """ + Validate an XML string against the SALT XML schema. + + The method raises a `ValueError` if the XML is not well-formed or does not conform + to the schema. + + Parameters + ---------- + xml + XML string. + + Raises + ------ + ValueError + If the XML is not well-formed or does not conform to the schema. + + """ + if not _schema: + _load_schema() + + try: + xml_doc = etree.parse(io.StringIO(xml)) + _schema.assertValid(xml_doc) + except (etree.DocumentInvalid, etree.XMLSyntaxError) as e: + raise ValueError(str(e)) + + +def _load_schema(): + with open(pathlib.Path(__file__).parent / "proposal.xsd", "r") as f: + schema_doc = etree.parse(f) + global _schema + _schema = etree.XMLSchema(schema_doc) diff --git a/tests/salt/models/test_util.py b/tests/salt/models/test_util.py new file mode 100644 index 0000000..c58ca38 --- /dev/null +++ b/tests/salt/models/test_util.py @@ -0,0 +1,37 @@ +import pytest + +from aeonlib.salt.models.serialize.util import validate_xml + + +def test_validate_non_well_formed_xml(): + """Test that an error is raised when you validate XML which is not well-formed.""" + xml = "" + with pytest.raises(ValueError): + validate_xml(xml) + + +def test_validate_invalid_xml(): + """ + Test that an error is raised when you validate XNL which does not conform to the + schema. + """ + xml = """ + + A + arcseconds/year + + """ + with pytest.raises(ValueError): + validate_xml(xml) + + +def test_validate_valid_xml(): + """Test that valid XML passes validation.""" + xml = """ + + 4.56 + arcseconds/year + + """ + validate_xml(xml) + assert True diff --git a/uv.lock b/uv.lock index cbc7f47..f65457e 100644 --- a/uv.lock +++ b/uv.lock @@ -22,6 +22,9 @@ lt = [ { name = "lxml-stubs" }, { name = "suds" }, ] +salt = [ + { name = "lxml" }, +] [package.dev-dependencies] codegen = [ @@ -37,13 +40,14 @@ requires-dist = [ { name = "astropy", specifier = ">=7.0.1" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "lxml", marker = "extra == 'lt'", specifier = ">=5.4.0" }, + { name = "lxml", marker = "extra == 'salt'", specifier = ">=6.0.1" }, { name = "lxml-stubs", marker = "extra == 'lt'", specifier = ">=0.5.1" }, { name = "p2api", marker = "extra == 'eso'", specifier = ">=1.0.10" }, { name = "pydantic", specifier = ">=2.11.1" }, { name = "pydantic-settings", specifier = ">=2.9.1" }, { name = "suds", marker = "extra == 'lt'", specifier = ">=1.2.0" }, ] -provides-extras = ["eso", "lt"] +provides-extras = ["eso", "lt", "salt"] [package.metadata.requires-dev] codegen = [ From 0118d98c7f909d1796a2e069b51a52d4cb826736 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 2 Feb 2026 16:23:33 +0200 Subject: [PATCH 063/171] Add a function for rendering Jinja templates --- pyproject.toml | 1 + src/aeonlib/salt/models/serialize/util.py | 32 +++++++++++++++++++++++ tests/salt/data/test.xml | 4 +++ tests/salt/models/test_util.py | 20 +++++++++++++- uv.lock | 2 ++ 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 tests/salt/data/test.xml diff --git a/pyproject.toml b/pyproject.toml index ae6b193..2b212c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ lt = [ "suds>=1.2.0", ] salt = [ + "jinja2>=3.1.6", "lxml>=6.0.1", ] diff --git a/src/aeonlib/salt/models/serialize/util.py b/src/aeonlib/salt/models/serialize/util.py index 5968898..3bed137 100644 --- a/src/aeonlib/salt/models/serialize/util.py +++ b/src/aeonlib/salt/models/serialize/util.py @@ -1,6 +1,7 @@ import io import pathlib +from jinja2 import Environment, PackageLoader, select_autoescape, BaseLoader from lxml import etree _schema: etree.XMLSchema | None = None @@ -39,3 +40,34 @@ def _load_schema(): schema_doc = etree.parse(f) global _schema _schema = etree.XMLSchema(schema_doc) + + +def render_template( + template_path: str, loader: BaseLoader | None = None, **kwargs +) -> str: + """ + Render a Jinja template. + + The first argument for this method is the path of the template to render, + as needed by the template loader. You may pass a `jinja2.BaseLoader` as the + `loader` argument for specifying how to load the template. The default is to look + in the `templates` folder of the `aeonlib.salt.models.serialize` package. Any + additional keyword arguments are passed on to Jinja's render function. + + Parameters + ---------- + template_path + Path of the template to render. + kwargs + Additional keyword arguments passed on to the render function. + + Returns + ------- + The rendered template. + """ + if not loader: + loader = PackageLoader("aeonlib.salt.models.serialize") + + env = Environment(loader=loader, autoescape=select_autoescape()) + template = env.get_template(template_path) + return template.render(**kwargs) diff --git a/tests/salt/data/test.xml b/tests/salt/data/test.xml new file mode 100644 index 0000000..ca9c633 --- /dev/null +++ b/tests/salt/data/test.xml @@ -0,0 +1,4 @@ + + {{ a }} + {{ b }} + diff --git a/tests/salt/models/test_util.py b/tests/salt/models/test_util.py index c58ca38..5b41f78 100644 --- a/tests/salt/models/test_util.py +++ b/tests/salt/models/test_util.py @@ -1,6 +1,9 @@ +import pathlib + import pytest +from jinja2 import FileSystemLoader -from aeonlib.salt.models.serialize.util import validate_xml +from aeonlib.salt.models.serialize.util import validate_xml, render_template def test_validate_non_well_formed_xml(): @@ -35,3 +38,18 @@ def test_validate_valid_xml(): """ validate_xml(xml) assert True + + +def test_render_template(): + """Test rendering a Jinja template.""" + loader = FileSystemLoader(pathlib.Path(__file__).parent.parent / "data") + rendered = render_template("test.xml", loader, a=1, b=2) + assert "1" in rendered + assert "2" in rendered + + +def test_render_template_with_escaping(): + """Test that input is escaped when rendering a Jinja template.""" + loader = FileSystemLoader(pathlib.Path(__file__).parent.parent / "data") + rendered = render_template("test.xml", loader, a="a >= 1 & a <= 5", b=2) + assert "a >= 1 & a <= 5" in rendered diff --git a/uv.lock b/uv.lock index f65457e..7e6cc6b 100644 --- a/uv.lock +++ b/uv.lock @@ -23,6 +23,7 @@ lt = [ { name = "suds" }, ] salt = [ + { name = "jinja2" }, { name = "lxml" }, ] @@ -39,6 +40,7 @@ dev = [ requires-dist = [ { name = "astropy", specifier = ">=7.0.1" }, { name = "httpx", specifier = ">=0.28.1" }, + { name = "jinja2", marker = "extra == 'salt'", specifier = ">=3.1.6" }, { name = "lxml", marker = "extra == 'lt'", specifier = ">=5.4.0" }, { name = "lxml", marker = "extra == 'salt'", specifier = ">=6.0.1" }, { name = "lxml-stubs", marker = "extra == 'lt'", specifier = ">=0.5.1" }, From e4b1032d1c0ae30ee556f1969dc50ce341e3f891 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 2 Feb 2026 16:24:11 +0200 Subject: [PATCH 064/171] Reformat a file --- src/aeonlib/salt/models/hrs_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py index 3b66ef9..1412c38 100644 --- a/src/aeonlib/salt/models/hrs_models.py +++ b/src/aeonlib/salt/models/hrs_models.py @@ -48,7 +48,7 @@ class Hrs(BaseModel): ) fibre_separation: Annotated[ Angle, GreaterEqual(16 * u.arcsec), LessEqual(63 * u.arcsec) - ] = (60 * u.arcsec) + ] = 60 * u.arcsec blue_arm: HrsDetector red_arm: HrsDetector From 26961d54d5989f227cf3642e1eebd18ff5b0eab3 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 2 Feb 2026 16:27:05 +0200 Subject: [PATCH 065/171] Move a test file --- tests/salt/models/{ => serialize}/test_util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/salt/models/{ => serialize}/test_util.py (97%) diff --git a/tests/salt/models/test_util.py b/tests/salt/models/serialize/test_util.py similarity index 97% rename from tests/salt/models/test_util.py rename to tests/salt/models/serialize/test_util.py index 5b41f78..520996f 100644 --- a/tests/salt/models/test_util.py +++ b/tests/salt/models/serialize/test_util.py @@ -42,7 +42,7 @@ def test_validate_valid_xml(): def test_render_template(): """Test rendering a Jinja template.""" - loader = FileSystemLoader(pathlib.Path(__file__).parent.parent / "data") + loader = FileSystemLoader(pathlib.Path(__file__).parent.parent.parent / "data") rendered = render_template("test.xml", loader, a=1, b=2) assert "1" in rendered assert "2" in rendered @@ -50,6 +50,6 @@ def test_render_template(): def test_render_template_with_escaping(): """Test that input is escaped when rendering a Jinja template.""" - loader = FileSystemLoader(pathlib.Path(__file__).parent.parent / "data") + loader = FileSystemLoader(pathlib.Path(__file__).parent.parent.parent / "data") rendered = render_template("test.xml", loader, a="a >= 1 & a <= 5", b=2) assert "a >= 1 & a <= 5" in rendered From ed2d6b1112afb15b2d3ef70b524edaf286678d6e Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 2 Feb 2026 17:10:40 +0200 Subject: [PATCH 066/171] Update docstrings --- src/aeonlib/salt/models/serialize/util.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/aeonlib/salt/models/serialize/util.py b/src/aeonlib/salt/models/serialize/util.py index 3bed137..eb9ecb3 100644 --- a/src/aeonlib/salt/models/serialize/util.py +++ b/src/aeonlib/salt/models/serialize/util.py @@ -14,6 +14,8 @@ def validate_xml(xml: str) -> None: The method raises a `ValueError` if the XML is not well-formed or does not conform to the schema. + This method is intended only for use in the serialization of SALT model instances. + Parameters ---------- xml @@ -54,10 +56,14 @@ def render_template( in the `templates` folder of the `aeonlib.salt.models.serialize` package. Any additional keyword arguments are passed on to Jinja's render function. + This method is intended only for use in the serialization of SALT model instances. + Parameters ---------- template_path Path of the template to render. + loader + Jinja template loader. kwargs Additional keyword arguments passed on to the render function. From 8a76eabb6667b45dbebf046e3e4ce0aabae71aed Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 3 Feb 2026 09:50:02 +0200 Subject: [PATCH 067/171] Add Pydantic serializers for transforming string values --- src/aeonlib/salt/models/serialize/util.py | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/aeonlib/salt/models/serialize/util.py b/src/aeonlib/salt/models/serialize/util.py index eb9ecb3..1aa31f1 100644 --- a/src/aeonlib/salt/models/serialize/util.py +++ b/src/aeonlib/salt/models/serialize/util.py @@ -3,6 +3,7 @@ from jinja2 import Environment, PackageLoader, select_autoescape, BaseLoader from lxml import etree +from pydantic import PlainSerializer _schema: etree.XMLSchema | None = None @@ -77,3 +78,39 @@ def render_template( env = Environment(loader=loader, autoescape=select_autoescape()) template = env.get_template(template_path) return template.render(**kwargs) + + +def _capitalize(s: str) -> str: + return s.capitalize() + + +CapitalizingSerializer = PlainSerializer(_capitalize) +""" +A serializer for capitalising string values. + +This serializer is only intended for use in the serialization of SALT data models. +""" + + +def _title(s: str) -> str: + return s.title() + + +TitleCaseSerializer = PlainSerializer(_title) +""" +A serializer for converting string values to title case. + +This serializer is only intended for use in the serialization of SALT data models. +""" + + +def _upper(s: str) -> str: + return s.upper() + + +UpperCaseSerializer = PlainSerializer(_upper) +""" +A serializer for converting string values to upper case. + +This serializer is only intended for use in the serialization of SALT data models. +""" From 9b52d7a5978aee6faa0217ca8238ce4b1cacb73e Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 4 Feb 2026 11:39:10 +0200 Subject: [PATCH 068/171] Add Pydantic serializers for transforming string values --- src/aeonlib/salt/models/block_models.py | 9 +++-- src/aeonlib/salt/models/hrs_models.py | 5 +-- src/aeonlib/salt/models/nirwals_models.py | 39 +++++++++++++++++----- src/aeonlib/salt/models/rss_models.py | 7 ++-- src/aeonlib/salt/models/salticam_models.py | 7 ++-- src/aeonlib/salt/models/types/nirwals.py | 2 +- 6 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index d99277e..25e7c10 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -18,6 +18,7 @@ from aeonlib.models import Angle, Window from aeonlib.salt.models import SaltSiderealTarget +from aeonlib.salt.models.serialize.util import CapitalizingSerializer from aeonlib.salt.models.types import ( PositiveDuration, SalticamFilter, @@ -103,7 +104,7 @@ class Block(BaseModel): identifier: str | None = None comments: str | None = None priority: Annotated[int, GreaterEqual(0), LessEqual(4)] - ranking: Literal["high", "medium", "low"] + ranking: Annotated[Literal["high", "medium", "low"], CapitalizingSerializer] num_visits: PositiveInt max_num_visits: PositiveInt | None = None min_nights_between_visits: NonNegativeInt = 0 @@ -113,7 +114,9 @@ class Block(BaseModel): acquisition: Acquisition instrument: None pool: str | None = None - data_notification: Literal["normal", "fast"] = "normal" + data_notification: Annotated[Literal["normal", "fast"], CapitalizingSerializer] = ( + "normal" + ) @model_validator(mode="after") def check_max_num_visits_is_at_least_num_visits(self) -> Self: @@ -157,7 +160,7 @@ class Constraints(BaseModel): Maximum allowed seeing. """ - transparency: SkyTransparency + transparency: Annotated[SkyTransparency, CapitalizingSerializer] max_lunar_phase_percentage: Annotated[NonNegativeFloat, LessEqual(100)] min_lunar_distance: Annotated[ Angle, GreaterEqual(0 * u.deg), LessEqual(180 * u.deg) diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py index 1412c38..a142c22 100644 --- a/src/aeonlib/salt/models/hrs_models.py +++ b/src/aeonlib/salt/models/hrs_models.py @@ -5,6 +5,7 @@ from astropy import units as u from pydantic import BaseModel, Field, PositiveInt, model_validator +from aeonlib.salt.models.serialize.util import UpperCaseSerializer from aeonlib.salt.models.types import HrsMode, HrsPrvCalibration, PositiveDuration from aeonlib.salt.validators import GreaterEqual, LessEqual from aeonlib.types import Angle @@ -40,7 +41,7 @@ class Hrs(BaseModel): """ num_cycles: PositiveInt = 1 - mode: HrsMode + mode: Annotated[HrsMode, UpperCaseSerializer] prv_calibration: HrsPrvCalibration | None = Field( default_factory=lambda data: ( "ThAr" if data["mode"] == "high stability" else None @@ -48,7 +49,7 @@ class Hrs(BaseModel): ) fibre_separation: Annotated[ Angle, GreaterEqual(16 * u.arcsec), LessEqual(63 * u.arcsec) - ] = 60 * u.arcsec + ] = (60 * u.arcsec) blue_arm: HrsDetector red_arm: HrsDetector diff --git a/src/aeonlib/salt/models/nirwals_models.py b/src/aeonlib/salt/models/nirwals_models.py index 38b5fa0..2f69e00 100644 --- a/src/aeonlib/salt/models/nirwals_models.py +++ b/src/aeonlib/salt/models/nirwals_models.py @@ -4,7 +4,7 @@ from typing import Annotated, Literal from astropy import units as u -from pydantic import BaseModel, PositiveInt, field_validator +from pydantic import BaseModel, PositiveInt, field_validator, PlainSerializer from aeonlib.salt.models.types import ( NirwalsCameraFilter, @@ -16,6 +16,10 @@ NirwalsSampling, PositiveDuration, ) +from aeonlib.salt.models.serialize.util import ( + UpperCaseSerializer, + CapitalizingSerializer, +) from aeonlib.salt.validators import GreaterEqual, LessEqual from aeonlib.types import Angle @@ -54,11 +58,11 @@ class Nirwals(BaseModel): """ num_cycles: PositiveInt = 1 - grating: NirwalsGrating + grating: Annotated[NirwalsGrating, UpperCaseSerializer] grating_angle: Annotated[Angle, GreaterEqual(0 * u.deg), LessEqual(100 * u.deg)] articulation_angle: Angle - filter: NirwalsFilter = "empty" - camera_filter: NirwalsCameraFilter + filter: NirwalsFilter = Annotated["empty", CapitalizingSerializer] + camera_filter: Annotated[NirwalsCameraFilter, CapitalizingSerializer] dither_pattern: list[NirwalsDitherPatternStep] include_arc: bool = True include_flat: bool @@ -117,17 +121,22 @@ class NirwalsDitherPatternStep(BaseModel): The number of ramps. This must be 1. """ - offset_type: NirwalsOffsetType + offset_type: Annotated[ + NirwalsOffsetType, + PlainSerializer(NirwalsDitherPatternStep.offset_type_serializer), + ] horizontal_offset: Annotated[ Angle, GreaterEqual(-100 * u.arcsec), LessEqual(100 * u.arcsec) ] vertical_offset: Annotated[ Angle, GreaterEqual(-100 * u.arcsec), LessEqual(100 * u.arcsec) ] - exposure_type: NirwalsExposureType + exposure_type: Annotated[NirwalsExposureType, CapitalizingSerializer] exposure_time: PositiveDuration - gain: NirwalsGain - sampling: NirwalsSampling + gain: Annotated[NirwalsGain, CapitalizingSerializer] + sampling: Annotated[ + NirwalsSampling, PlainSerializer(NirwalsDitherPatternStep.sampling_serializer) + ] num_reads: Literal[1] = 1 num_ramps: Literal[1] = 1 @@ -152,3 +161,17 @@ def num_groups(self): math.floor(float(self.exposure_time / (self.num_reads * frame_rate))) ) return max(1, groups) + + @staticmethod + def _offset_type_serializer(value: NirwalsOffsetType) -> str: + if value == "FIF offset": + return "FIF Offset" + else: + return value.title() + + @staticmethod + def _sampling_serializer(value: NirwalsSampling) -> str: + if value == "up-the-ramp": + return "Up-the-Ramp Group" + else: + raise ValueError(f"Sampling cannot be serialized: {value}") diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index f7dc95c..5695484 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -14,6 +14,7 @@ ) from aeonlib.types import Angle +from aeonlib.salt.models.serialize.util import TitleCaseSerializer from aeonlib.salt.models.types import ( PositiveDuration, RssGain, @@ -326,9 +327,9 @@ class RssDetector(BaseModel): exposure_time: PositiveDuration num_exposures: int = 1 - readout_mode: RssReadoutMode = "normal" - gain: RssGain - readout_speed: RssReadoutSpeed + readout_mode: Annotated[RssReadoutMode, TitleCaseSerializer] = "normal" + gain: Annotated[RssGain, TitleCaseSerializer] + readout_speed: Annotated[RssReadoutSpeed, TitleCaseSerializer] num_prebinned_rows: Annotated[int, GreaterEqual(1), LessEqual(9)] num_prebinned_columns: Annotated[int, GreaterEqual(1), LessEqual(9)] window_height: Annotated[ diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index 31a0531..425b2e7 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -12,6 +12,7 @@ SalticamFilter, AstropyQuantityTypeAnnotation, ) +from aeonlib.salt.models.serialize.util import CapitalizingSerializer from aeonlib.salt.validators import GreaterEqual, LessEqual @@ -86,9 +87,9 @@ class SalticamDetector(BaseModel): """ num_exposures: PositiveInt - readout_mode: Literal["normal"] = "normal" - gain: Literal["bright", "faint"] - readout_speed: Literal["fast", "slow"] + readout_mode: Annotated[Literal["normal"], CapitalizingSerializer] = "normal" + gain: Annotated[Literal["bright", "faint"], CapitalizingSerializer] + readout_speed: Annotated[Literal["fast", "slow"], CapitalizingSerializer] num_prebinned_rows: Annotated[int, GreaterEqual(1), LessEqual(9)] num_prebinned_columns: Annotated[int, GreaterEqual(1), LessEqual(9)] diff --git a/src/aeonlib/salt/models/types/nirwals.py b/src/aeonlib/salt/models/types/nirwals.py index 03231ec..fbad511 100644 --- a/src/aeonlib/salt/models/types/nirwals.py +++ b/src/aeonlib/salt/models/types/nirwals.py @@ -1,7 +1,7 @@ from typing import Literal -NirwalsGrating = Literal["NG0950"] +NirwalsGrating = Literal["ng0950"] """A NIRWALS grating.""" From c0f8dd95ab76f7ee717d92fd5dc091635adfb01b Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 4 Feb 2026 13:02:07 +0200 Subject: [PATCH 069/171] Fix a typo in a docstring --- src/aeonlib/salt/models/rss_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 5695484..8e5cb84 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -236,7 +236,7 @@ class RssPolarimetry(BaseModel): For example, the "linear" and "circular" patterns could be given as:: linear = RssPolarimetry(wave_plate_pattern="linear") - circular = RssPolarimetry(wave_plate_pattern="linear") + circular = RssPolarimetry(wave_plate_pattern="circular") or as:: From 960e935864eb2bbe7cf7b88369d4bf51a550718e Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 5 Feb 2026 08:55:43 +0200 Subject: [PATCH 070/171] Add a validator for turning strings into lower case --- src/aeonlib/salt/models/serialize/util.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/serialize/util.py b/src/aeonlib/salt/models/serialize/util.py index 1aa31f1..abd9392 100644 --- a/src/aeonlib/salt/models/serialize/util.py +++ b/src/aeonlib/salt/models/serialize/util.py @@ -1,9 +1,10 @@ import io import pathlib +from typing import Any from jinja2 import Environment, PackageLoader, select_autoescape, BaseLoader from lxml import etree -from pydantic import PlainSerializer +from pydantic import PlainSerializer, BeforeValidator _schema: etree.XMLSchema | None = None @@ -80,6 +81,15 @@ def render_template( return template.render(**kwargs) +def _lower(s: Any) -> Any: + if isinstance(s, str): + return s.lower() + return s + + +LowerCaseValidator = BeforeValidator(_lower) + + def _capitalize(s: str) -> str: return s.capitalize() From 8c55f8e18fb2b02738526be878bfb2f3f1c22b8e Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 5 Feb 2026 09:51:41 +0200 Subject: [PATCH 071/171] Handle None values --- src/aeonlib/salt/models/{serialize => }/util.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) rename src/aeonlib/salt/models/{serialize => }/util.py (92%) diff --git a/src/aeonlib/salt/models/serialize/util.py b/src/aeonlib/salt/models/util.py similarity index 92% rename from src/aeonlib/salt/models/serialize/util.py rename to src/aeonlib/salt/models/util.py index abd9392..b03a327 100644 --- a/src/aeonlib/salt/models/serialize/util.py +++ b/src/aeonlib/salt/models/util.py @@ -90,7 +90,9 @@ def _lower(s: Any) -> Any: LowerCaseValidator = BeforeValidator(_lower) -def _capitalize(s: str) -> str: +def _capitalize(s: str | None) -> str | None: + if s is None: + return None return s.capitalize() @@ -102,7 +104,9 @@ def _capitalize(s: str) -> str: """ -def _title(s: str) -> str: +def _title(s: str | None) -> str | None: + if s is None: + return None return s.title() @@ -114,7 +118,9 @@ def _title(s: str) -> str: """ -def _upper(s: str) -> str: +def _upper(s: str | None) -> str | None: + if s is None: + return None return s.upper() From fafac3971ff202d486f71a8d558e8b471f147efe Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 5 Feb 2026 11:38:05 +0200 Subject: [PATCH 072/171] Move all model string values to lower case In some cases non-lower case values are explicitly allowed in the type definition (such as for Salticam filter names), but such values are still converted to lower case during validation. --- src/aeonlib/salt/models/block_models.py | 19 +++--- src/aeonlib/salt/models/hrs_models.py | 28 ++++++--- src/aeonlib/salt/models/nirwals_models.py | 50 ++++++++++++---- .../salt/models/{serialize => }/proposal.xsd | 0 src/aeonlib/salt/models/rss_models.py | 23 ++++++- src/aeonlib/salt/models/salticam_models.py | 5 +- src/aeonlib/salt/models/types/__init__.py | 3 +- src/aeonlib/salt/models/types/hrs.py | 2 +- src/aeonlib/salt/models/types/nirwals.py | 4 +- src/aeonlib/salt/models/types/rss.py | 2 +- src/aeonlib/salt/models/types/salticam.py | 60 +++++++++++++++++++ tests/salt/models/serialize/test_util.py | 2 +- tests/salt/models/test_hrs_models.py | 4 +- 13 files changed, 161 insertions(+), 41 deletions(-) rename src/aeonlib/salt/models/{serialize => }/proposal.xsd (100%) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 25e7c10..00d6ed7 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -18,10 +18,11 @@ from aeonlib.models import Angle, Window from aeonlib.salt.models import SaltSiderealTarget -from aeonlib.salt.models.serialize.util import CapitalizingSerializer +from aeonlib.salt.models.util import LowerCaseValidator, CapitalizingSerializer from aeonlib.salt.models.types import ( PositiveDuration, SalticamFilter, + SalticamFilterSerializer, SkyTransparency, ) from aeonlib.salt.validators import GreaterEqual, LessEqual, check_in_visibility_range @@ -104,7 +105,9 @@ class Block(BaseModel): identifier: str | None = None comments: str | None = None priority: Annotated[int, GreaterEqual(0), LessEqual(4)] - ranking: Annotated[Literal["high", "medium", "low"], CapitalizingSerializer] + ranking: Annotated[ + Literal["high", "medium", "low"], LowerCaseValidator, CapitalizingSerializer + ] num_visits: PositiveInt max_num_visits: PositiveInt | None = None min_nights_between_visits: NonNegativeInt = 0 @@ -114,9 +117,9 @@ class Block(BaseModel): acquisition: Acquisition instrument: None pool: str | None = None - data_notification: Annotated[Literal["normal", "fast"], CapitalizingSerializer] = ( - "normal" - ) + data_notification: Annotated[ + Literal["normal", "fast"], LowerCaseValidator, CapitalizingSerializer + ] = "normal" @model_validator(mode="after") def check_max_num_visits_is_at_least_num_visits(self) -> Self: @@ -160,7 +163,7 @@ class Constraints(BaseModel): Maximum allowed seeing. """ - transparency: Annotated[SkyTransparency, CapitalizingSerializer] + transparency: Annotated[SkyTransparency, LowerCaseValidator, CapitalizingSerializer] max_lunar_phase_percentage: Annotated[NonNegativeFloat, LessEqual(100)] min_lunar_distance: Annotated[ Angle, GreaterEqual(0 * u.deg), LessEqual(180 * u.deg) @@ -199,7 +202,9 @@ class Acquisition(BaseModel): """ finder_charts: list[FilePath] - filter: SalticamFilter = "Johnson V" + filter: Annotated[SalticamFilter, LowerCaseValidator, SalticamFilterSerializer] = ( + "Johnson V" + ) exposure_time: PositiveDuration = 1.0 * u.s reference_star: ReferenceStar | None = None include_focused_image: bool = False diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py index a142c22..74d1381 100644 --- a/src/aeonlib/salt/models/hrs_models.py +++ b/src/aeonlib/salt/models/hrs_models.py @@ -3,9 +3,9 @@ from typing import Annotated, Self from astropy import units as u -from pydantic import BaseModel, Field, PositiveInt, model_validator +from pydantic import BaseModel, Field, PositiveInt, model_validator, PlainSerializer -from aeonlib.salt.models.serialize.util import UpperCaseSerializer +from aeonlib.salt.models.util import LowerCaseValidator, UpperCaseSerializer from aeonlib.salt.models.types import HrsMode, HrsPrvCalibration, PositiveDuration from aeonlib.salt.validators import GreaterEqual, LessEqual from aeonlib.types import Angle @@ -41,24 +41,28 @@ class Hrs(BaseModel): """ num_cycles: PositiveInt = 1 - mode: Annotated[HrsMode, UpperCaseSerializer] - prv_calibration: HrsPrvCalibration | None = Field( + mode: Annotated[HrsMode, LowerCaseValidator, UpperCaseSerializer] + prv_calibration: Annotated[ + HrsPrvCalibration | None, + LowerCaseValidator, + PlainSerializer(Hrs.serialize_prv_calibration), + ] = Field( default_factory=lambda data: ( - "ThAr" if data["mode"] == "high stability" else None + "thar" if data["mode"] == "high stability" else None ) ) fibre_separation: Annotated[ Angle, GreaterEqual(16 * u.arcsec), LessEqual(63 * u.arcsec) - ] = (60 * u.arcsec) + ] = 60 * u.arcsec blue_arm: HrsDetector red_arm: HrsDetector @model_validator(mode="after") def check_prv_calibration(self) -> Self: if self.mode == "high stability": - if self.prv_calibration != "ThAr": + if self.prv_calibration != "thar": raise ValueError( - 'prv_calibration must be "ThAr" for the high stability mode.' + 'prv_calibration must be "ThAr" (or "thar") for the high stability mode.' ) else: if self.prv_calibration is not None: @@ -67,6 +71,14 @@ def check_prv_calibration(self) -> Self: ) return self + @staticmethod + def serialize_prv_calibration(value: str | None) -> str | None: + if value is None: + return None + if value == "thar": + return "ThAr" + raise ValueError(f"Precision radial velocity cannot be serialized: {value}") + class HrsDetector(BaseModel): """ diff --git a/src/aeonlib/salt/models/nirwals_models.py b/src/aeonlib/salt/models/nirwals_models.py index 2f69e00..386a6eb 100644 --- a/src/aeonlib/salt/models/nirwals_models.py +++ b/src/aeonlib/salt/models/nirwals_models.py @@ -1,10 +1,16 @@ from __future__ import annotations import math -from typing import Annotated, Literal +from typing import Annotated, Literal, Any from astropy import units as u -from pydantic import BaseModel, PositiveInt, field_validator, PlainSerializer +from pydantic import ( + BaseModel, + PositiveInt, + field_validator, + PlainSerializer, + BeforeValidator, +) from aeonlib.salt.models.types import ( NirwalsCameraFilter, @@ -16,7 +22,8 @@ NirwalsSampling, PositiveDuration, ) -from aeonlib.salt.models.serialize.util import ( +from aeonlib.salt.models.util import ( + LowerCaseValidator, UpperCaseSerializer, CapitalizingSerializer, ) @@ -58,11 +65,15 @@ class Nirwals(BaseModel): """ num_cycles: PositiveInt = 1 - grating: Annotated[NirwalsGrating, UpperCaseSerializer] + grating: Annotated[NirwalsGrating, LowerCaseValidator, UpperCaseSerializer] grating_angle: Annotated[Angle, GreaterEqual(0 * u.deg), LessEqual(100 * u.deg)] articulation_angle: Angle - filter: NirwalsFilter = Annotated["empty", CapitalizingSerializer] - camera_filter: Annotated[NirwalsCameraFilter, CapitalizingSerializer] + filter: Annotated[NirwalsFilter, LowerCaseValidator, CapitalizingSerializer] = ( + "empty" + ) + camera_filter: Annotated[ + NirwalsCameraFilter, LowerCaseValidator, CapitalizingSerializer + ] dither_pattern: list[NirwalsDitherPatternStep] include_arc: bool = True include_flat: bool @@ -123,7 +134,8 @@ class NirwalsDitherPatternStep(BaseModel): offset_type: Annotated[ NirwalsOffsetType, - PlainSerializer(NirwalsDitherPatternStep.offset_type_serializer), + LowerCaseValidator, + PlainSerializer(NirwalsDitherPatternStep.serialize_offset_type), ] horizontal_offset: Annotated[ Angle, GreaterEqual(-100 * u.arcsec), LessEqual(100 * u.arcsec) @@ -131,11 +143,15 @@ class NirwalsDitherPatternStep(BaseModel): vertical_offset: Annotated[ Angle, GreaterEqual(-100 * u.arcsec), LessEqual(100 * u.arcsec) ] - exposure_type: Annotated[NirwalsExposureType, CapitalizingSerializer] + exposure_type: Annotated[ + NirwalsExposureType, LowerCaseValidator, CapitalizingSerializer + ] exposure_time: PositiveDuration - gain: Annotated[NirwalsGain, CapitalizingSerializer] + gain: Annotated[NirwalsGain, LowerCaseValidator, CapitalizingSerializer] sampling: Annotated[ - NirwalsSampling, PlainSerializer(NirwalsDitherPatternStep.sampling_serializer) + NirwalsSampling, + BeforeValidator(NirwalsDitherPatternStep.validate_sampling), + PlainSerializer(NirwalsDitherPatternStep.serialize_sampling), ] num_reads: Literal[1] = 1 num_ramps: Literal[1] = 1 @@ -163,15 +179,23 @@ def num_groups(self): return max(1, groups) @staticmethod - def _offset_type_serializer(value: NirwalsOffsetType) -> str: - if value == "FIF offset": + def serialize_offset_type(value: NirwalsOffsetType) -> str: + if value == "fif offset": return "FIF Offset" else: return value.title() @staticmethod - def _sampling_serializer(value: NirwalsSampling) -> str: + def serialize_sampling(value: NirwalsSampling) -> str: if value == "up-the-ramp": return "Up-the-Ramp Group" else: raise ValueError(f"Sampling cannot be serialized: {value}") + + @staticmethod + def validate_sampling(value: Any) -> Any: + if isinstance(value, str): + value = value.lower() + if value == "up-the-ramp group": + value = "up-the-ramp" + return value diff --git a/src/aeonlib/salt/models/serialize/proposal.xsd b/src/aeonlib/salt/models/proposal.xsd similarity index 100% rename from src/aeonlib/salt/models/serialize/proposal.xsd rename to src/aeonlib/salt/models/proposal.xsd diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 8e5cb84..93f8b35 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -11,10 +11,16 @@ PositiveInt, field_validator, model_validator, + PlainSerializer, ) +from aeonlib.salt.models.types.salticam import serialize_salticam_filter from aeonlib.types import Angle -from aeonlib.salt.models.serialize.util import TitleCaseSerializer +from aeonlib.salt.models.util import ( + TitleCaseSerializer, + LowerCaseValidator, + UpperCaseSerializer, +) from aeonlib.salt.models.types import ( PositiveDuration, RssGain, @@ -100,10 +106,21 @@ class RssImaging(BaseModel): Whether a nighttime flat should be taken for the observation. """ - filter: RssImagingFilter | SalticamFilter + filter: Annotated[ + RssImagingFilter | SalticamFilter, + LowerCaseValidator, + PlainSerializer(RssImaging.serialize_filter), + ] polarimetry: RssPolarimetry | None = None include_flat: bool + @staticmethod + def serialize_filter(value: str) -> str: + if value.startswith("pi"): + return value.upper() + else: + return serialize_salticam_filter(value) + _WavePlatePattern = ( Literal["linear", "linear hi", "circular", "all-Stokes"] @@ -218,7 +235,7 @@ class RssSlitMaskIFUSpectroscopy(RssSpectroscopy): The barcode of the slit mask IFU, such as "PF0200N001". """ - slit_mask_ifu: RssSlitMaskIFU + slit_mask_ifu: Annotated[RssSlitMaskIFU, LowerCaseValidator, UpperCaseSerializer] class RssPolarimetry(BaseModel): diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index 425b2e7..ae8e927 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -10,9 +10,10 @@ from aeonlib.salt.models.types import ( PositiveDuration, SalticamFilter, + SalticamFilterSerializer, AstropyQuantityTypeAnnotation, ) -from aeonlib.salt.models.serialize.util import CapitalizingSerializer +from aeonlib.salt.models.util import CapitalizingSerializer, LowerCaseValidator from aeonlib.salt.validators import GreaterEqual, LessEqual @@ -70,7 +71,7 @@ class SalticamFilterSequenceStep(BaseModel): seconds. """ - filter: SalticamFilter + filter: Annotated[SalticamFilter, LowerCaseValidator, SalticamFilterSerializer] exposure_time: PositiveDuration diff --git a/src/aeonlib/salt/models/types/__init__.py b/src/aeonlib/salt/models/types/__init__.py index 1e9ff99..baa5d0d 100644 --- a/src/aeonlib/salt/models/types/__init__.py +++ b/src/aeonlib/salt/models/types/__init__.py @@ -20,7 +20,7 @@ RssReadoutSpeed, RssSlitMaskIFU, ) -from .salticam import SalticamFilter +from .salticam import SalticamFilter, SalticamFilterSerializer from .target import MagnitudeBandpass, TargetType __all__ = [ @@ -45,6 +45,7 @@ "RssReadoutSpeed", "RssSlitMaskIFU", "SalticamFilter", + "SalticamFilterSerializer", "SkyTransparency", "TargetType", ] diff --git a/src/aeonlib/salt/models/types/hrs.py b/src/aeonlib/salt/models/types/hrs.py index 8bb9634..0ddac08 100644 --- a/src/aeonlib/salt/models/types/hrs.py +++ b/src/aeonlib/salt/models/types/hrs.py @@ -6,5 +6,5 @@ """An HRS instrument mode.""" -HrsPrvCalibration = Literal["ThAr"] +HrsPrvCalibration = Literal["ThAr", "thar"] """An HRS precision radial velocity calibration.""" diff --git a/src/aeonlib/salt/models/types/nirwals.py b/src/aeonlib/salt/models/types/nirwals.py index fbad511..22b9368 100644 --- a/src/aeonlib/salt/models/types/nirwals.py +++ b/src/aeonlib/salt/models/types/nirwals.py @@ -15,7 +15,7 @@ """A NIRWALS camera filter.""" -NirwalsOffsetType = Literal["FIF offset", "tracker guided offset"] +NirwalsOffsetType = Literal["FIF offset", "fif offset", "tracker guided offset"] """An offset type for NIRWALS.""" @@ -27,5 +27,5 @@ """A gain option for NIRWALS.""" -NirwalsSampling = Literal["up-the-ramp"] +NirwalsSampling = Literal["up-the-ramp", "up-the-ramp group"] """A sampling mode for NIRWALS.""" diff --git a/src/aeonlib/salt/models/types/rss.py b/src/aeonlib/salt/models/types/rss.py index d0ac04b..a6e6a76 100644 --- a/src/aeonlib/salt/models/types/rss.py +++ b/src/aeonlib/salt/models/types/rss.py @@ -53,7 +53,7 @@ """An RSS grating.""" -RssSlitMaskIFU = Literal["PF0200N001"] +RssSlitMaskIFU = Literal["PF0200N001", "pf0200n001"] """A slit mask integrated field unit (IFU) for RSS.""" RssReadoutMode = Literal["normal", "frame transfer", "slot mode"] diff --git a/src/aeonlib/salt/models/types/salticam.py b/src/aeonlib/salt/models/types/salticam.py index dd14d6f..54f91d5 100644 --- a/src/aeonlib/salt/models/types/salticam.py +++ b/src/aeonlib/salt/models/types/salticam.py @@ -1,29 +1,89 @@ from typing import Literal +from pydantic import PlainSerializer SalticamFilter = Literal[ "Fused silica clear", + "fused silica clear", "Johnson U", + "johnson u", "Johnson B", + "johnson b", "Johnson V", + "johnson v", "Cousins R", + "cousins r", "Cousins I", + "cousins i", "380nm 40nm FWHM", + "380nm 40nm fwhm", "SDSS u'", + "sdss u'", "SDSS g'", + "sdss g'", "SDSS r'", + "sdss r'", "SDSS i'", + "sdss i'", "SDSS z'", + "sdss z'", "H-alpha", + "h-alpha", "H-beta narrow", + "h-beta narrow", "H-beta wide", + "h-beta wide", "Stroemgren u", + "stroemgren u", "Stroemgren v", + "stroemgren v", "Stroemgren b", + "stroemgren b", "Stroemgren y", + "stroemgren y", "SRE 1", + "sre 1", "SRE 2", + "sre 2", "SRE 3", + "sre 3", "SRE 4", + "sre 4", ] """A filter for Salticam.""" + + +def serialize_salticam_filter(value: str) -> str: + value = value.lower() + translation_table = { + "fused silica clear": "Fused silica clear", + "johnson u": "Johnson U", + "johnson b": "Johnson B", + "johnson v": "Johnson V", + "cousins r": "Cousins R", + "cousins i": "Cousins I", + "380nm 40nm fwhm": "380nm 40nm FWHM", + "sdss u'": "SDSS u'", + "sdss g'": "SDSS g'", + "sdss r'": "SDSS r'", + "sdss i'": "SDSS i'", + "sdss z'": "SDSS z'", + "h-alpha": "H-alpha", + "h-beta narrow": "H-beta narrow", + "h-beta wide": "H-beta wide", + "stroemgren u": "Stroemgren u", + "stroemgren v": "Stroemgren v", + "stroemgren b": "Stroemgren b", + "stroemgren y": "Stroemgren y", + "sre 1": "SRE 1", + "sre 2": "SRE 2", + "sre 3": "SRE 3", + "sre 4": "SRE 4", + } + serialized = translation_table.get(value) + if serialized is None: + raise ValueError(f"Filter name cannot be serialized: {value}") + return serialized + + +SalticamFilterSerializer = PlainSerializer(serialize_salticam_filter) diff --git a/tests/salt/models/serialize/test_util.py b/tests/salt/models/serialize/test_util.py index 520996f..ddec98e 100644 --- a/tests/salt/models/serialize/test_util.py +++ b/tests/salt/models/serialize/test_util.py @@ -3,7 +3,7 @@ import pytest from jinja2 import FileSystemLoader -from aeonlib.salt.models.serialize.util import validate_xml, render_template +from aeonlib.salt.models.util import validate_xml, render_template def test_validate_non_well_formed_xml(): diff --git a/tests/salt/models/test_hrs_models.py b/tests/salt/models/test_hrs_models.py index 6984dd0..3296ea2 100644 --- a/tests/salt/models/test_hrs_models.py +++ b/tests/salt/models/test_hrs_models.py @@ -17,7 +17,7 @@ def test_hrs(self, base_hrs): ("low resolution", None), ("medium resolution", None), ("high resolution", None), - ("high stability", "ThAr"), + ("high stability", "thar"), ], ) def test_default_prv_calibration(self, mode: HrsMode, prv_calibration, base_hrs): @@ -47,7 +47,7 @@ def test_default_prv_calibration(self, mode: HrsMode, prv_calibration, base_hrs) def test_allowed_prv_calibration_depends_on_mode( self, mode: HrsMode, prv_calibration_is_none, base_hrs ): - # Test that the precision radial velocity calibration must be "ThAr" for the + # Test that the precision radial velocity calibration must be "thar" for the # high stability mode and None for all other modes. hrs_data = base_hrs.model_dump() del hrs_data["mode"] From d8d81f54154724bae2010954e31cddae233c9bd8 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 6 Feb 2026 12:32:12 +0200 Subject: [PATCH 073/171] Handle polarimetry patterns specified as a string The string is replaced with the corresponding pattern of angles. --- src/aeonlib/salt/models/rss_models.py | 60 ++++++++++++++++++++------- src/aeonlib/salt/models/util.py | 55 ++++++++++++++++++++++++ tests/salt/models/test_rss_models.py | 35 ++++++++++++++++ 3 files changed, 134 insertions(+), 16 deletions(-) diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 93f8b35..3f9eede 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -12,6 +12,8 @@ field_validator, model_validator, PlainSerializer, + BeforeValidator, + AfterValidator, ) from aeonlib.salt.models.types.salticam import serialize_salticam_filter @@ -20,6 +22,11 @@ TitleCaseSerializer, LowerCaseValidator, UpperCaseSerializer, + LINEAR_POLARIMETRY_PATTERN, + LINEAR_HI_POLARIMETRY_PATTERN, + CIRCULAR_POLARIMETRY_PATTERN, + CIRCULAR_HI_POLARIMETRY_PATTRERN, + ALL_STOKES_POLARIMETRY_PATTERN, ) from aeonlib.salt.models.types import ( PositiveDuration, @@ -123,7 +130,9 @@ def serialize_filter(value: str) -> str: _WavePlatePattern = ( - Literal["linear", "linear hi", "circular", "all-Stokes"] + Literal[ + "linear", "linear hi", "circular", "circular hi", "all-Stokes", "all-stokes" + ] | list[tuple[Angle | None, Angle | None]] ) @@ -274,26 +283,51 @@ class RssPolarimetry(BaseModel): A wave plate pattern may have up to 8 steps. """ - wave_plate_pattern: _WavePlatePattern + wave_plate_pattern: Annotated[ + _WavePlatePattern, + BeforeValidator(RssPolarimetry.validate_pattern_before), + AfterValidator(RssPolarimetry.validate_pattern_after), + ] + + @staticmethod + def validate_pattern_before(value: _WavePlatePattern) -> _WavePlatePattern: + if isinstance(value, str): + return value.lower() + return value + + @staticmethod + def validate_pattern_after(value: _WavePlatePattern) -> _WavePlatePattern: + if value == "linear": + value = LINEAR_POLARIMETRY_PATTERN + elif value == "linear hi": + value = LINEAR_HI_POLARIMETRY_PATTERN + elif value == "circular": + value = CIRCULAR_POLARIMETRY_PATTERN + elif value == "circular hi": + value = CIRCULAR_HI_POLARIMETRY_PATTRERN + elif value == "all-stokes": + value = ALL_STOKES_POLARIMETRY_PATTERN - @field_validator("wave_plate_pattern", mode="after") - @classmethod - def check_pattern_size(cls, value: _WavePlatePattern) -> _WavePlatePattern: if isinstance(value, str): - return value + raise ValueError(f"Unsupported string value: {value}") if len(value) < 1 or len(value) > 8: raise ValueError("The wave plate pattern must have between 1 and 8 steps.") + RssPolarimetry._check_angle_values(value) + return value - @classmethod - def _check_pattern_step(cls, step: tuple[Angle, Angle]) -> None: + @staticmethod + def _check_pattern_step(step: tuple[Angle | None, Angle | None]) -> None: error = ( "Each angle in a wave plate pattern must be a multiple of 11.25 degrees " "between 0 degrees (inclusive) and 360 degrees (exclusive)." ) for angle in step: + if angle is None: + continue + if angle < 0 * u.deg or angle >= 360 * u.deg: raise ValueError(error) @@ -303,17 +337,11 @@ def _check_pattern_step(cls, step: tuple[Angle, Angle]) -> None: if abs(round(x) - x) > 1e-6: raise ValueError(error) - @field_validator("wave_plate_pattern", mode="after") - @classmethod - def check_angle_values(cls, value: _WavePlatePattern) -> _WavePlatePattern: - if isinstance(value, str): - return value - + @staticmethod + def _check_angle_values(value: _WavePlatePattern) -> _WavePlatePattern: for step in value: RssPolarimetry._check_pattern_step(step) - return value - class RssDetector(BaseModel): """ diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index b03a327..4f86ea3 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -2,6 +2,8 @@ import pathlib from typing import Any +import astropy.units as u +from astropy.coordinates import Angle from jinja2 import Environment, PackageLoader, select_autoescape, BaseLoader from lxml import etree from pydantic import PlainSerializer, BeforeValidator @@ -9,6 +11,59 @@ _schema: etree.XMLSchema | None = None +LINEAR_POLARIMETRY_PATTERN = [ + (Angle(0 * u.deg), None), + (Angle(45 * u.deg), None), + (Angle(22.5 * u.deg), None), + (Angle(67.5 * u.deg), None), +] +# The half and quarter wave plate angles for the linear polarimetry pattern. + + +LINEAR_HI_POLARIMETRY_PATTERN = [ + (Angle(0 * u.deg), None), + (Angle(45 * u.deg), None), + (Angle(22.5 * u.deg), None), + (Angle(67.5 * u.deg), None), + (Angle(11.25 * u.deg), None), + (Angle(56.25 * u.deg), None), + (Angle(33.75 * u.deg), None), + (Angle(78.75 * u.deg), None), +] +# The half and quarter wave plate angles for the linear-hi polarimetry pattern. + + +CIRCULAR_POLARIMETRY_PATTERN = [ + (Angle(0 * u.deg), Angle(45 * u.deg)), + (Angle(0 * u.deg), Angle(315 * u.deg)), +] +# The half and quarter wave plate angles for the circular polarimetry pattern. + + +CIRCULAR_HI_POLARIMETRY_PATTRERN = [ + (Angle(0 * u.deg), Angle(45 * u.deg)), + (Angle(0 * u.deg), Angle(315 * u.deg)), + (Angle(22.5 * u.deg), Angle(315 * u.deg)), + (Angle(22.5 * u.deg), Angle(45 * u.deg)), + (Angle(45 * u.deg), Angle(45 * u.deg)), + (Angle(45 * u.deg), Angle(315 * u.deg)), + (Angle(67.5 * u.deg), Angle(315 * u.deg)), + (Angle(67.5 * u.deg), Angle(45 * u.deg)), +] +# The half and quarter wave plate angles for the circular-hi polarimetry pattern. + + +ALL_STOKES_POLARIMETRY_PATTERN = [ + (Angle(0 * u.deg), Angle(0 * u.deg)), + (Angle(45 * u.deg), Angle(0 * u.deg)), + (Angle(22.5 * u.deg), Angle(0 * u.deg)), + (Angle(67.5 * u.deg), Angle(0 * u.deg)), + (Angle(0 * u.deg), Angle(45 * u.deg)), + (Angle(0 * u.deg), Angle(315 * u.deg)), +] +# The half and quarter wave plate angles for the all-Stokes polarimetry pattern. + + def validate_xml(xml: str) -> None: """ Validate an XML string against the SALT XML schema. diff --git a/tests/salt/models/test_rss_models.py b/tests/salt/models/test_rss_models.py index ec9168c..580d16a 100644 --- a/tests/salt/models/test_rss_models.py +++ b/tests/salt/models/test_rss_models.py @@ -6,6 +6,13 @@ from pydantic import ValidationError from aeonlib.salt.models import RssDitherPattern, RssPolarimetry, RssSpectroscopy +from aeonlib.salt.models.util import ( + LINEAR_POLARIMETRY_PATTERN, + LINEAR_HI_POLARIMETRY_PATTERN, + CIRCULAR_POLARIMETRY_PATTERN, + CIRCULAR_HI_POLARIMETRY_PATTRERN, + ALL_STOKES_POLARIMETRY_PATTERN, +) class TestRss: @@ -94,6 +101,34 @@ def test_angles_must_have_allowed_value(self, angle, expectation): RssPolarimetry(wave_plate_pattern=[(angle, 45 * u.deg)]) RssPolarimetry(wave_plate_pattern=[(45 * u.deg, angle)]) + def test_linear_pattern(self): + # That the string "linear" is internally converted into the linear pattern. + polarimetry = RssPolarimetry(wave_plate_pattern="linear") + assert polarimetry.wave_plate_pattern == LINEAR_POLARIMETRY_PATTERN + + def test_linear_hi_pattern(self): + # That the string "linear hi" is internally converted into the linear hi + # pattern. + polarimetry = RssPolarimetry(wave_plate_pattern="linear hi") + assert polarimetry.wave_plate_pattern == LINEAR_HI_POLARIMETRY_PATTERN + + def test_circular_pattern(self): + # That the string "circular" is internally converted into the circular pattern. + polarimetry = RssPolarimetry(wave_plate_pattern="circular") + assert polarimetry.wave_plate_pattern == CIRCULAR_POLARIMETRY_PATTERN + + def test_circular_hi_pattern(self): + # That the string "circular hi" is internally converted into the circular hi + # pattern. + polarimetry = RssPolarimetry(wave_plate_pattern="circular hi") + assert polarimetry.wave_plate_pattern == CIRCULAR_HI_POLARIMETRY_PATTRERN + + def test_all_stokes_pattern(self): + # That the string "all-Stokes" is internally converted into the all-Stokes + # pattern. + polarimetry = RssPolarimetry(wave_plate_pattern="all-Stokes") + assert polarimetry.wave_plate_pattern == ALL_STOKES_POLARIMETRY_PATTERN + class TestRssLongslitSpectroscopy: def test_rss_longslit_spectroscopy(self, base_rss_longslit_spectroscopy): From 6060dd73b3f5eac585228541a9be4372397cc8ab Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 6 Feb 2026 18:04:53 +0200 Subject: [PATCH 074/171] Add templates for serializing Salticam instances --- .../models/serialize/templates/salticam.xml | 38 +++++++++++++++++++ .../serialize/templates/salticam_detector.xml | 9 +++++ .../templates/salticam_dither_pattern.xml | 9 +++++ tests/salt/models/serialize/test_templates.py | 35 +++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 src/aeonlib/salt/models/serialize/templates/salticam.xml create mode 100644 src/aeonlib/salt/models/serialize/templates/salticam_detector.xml create mode 100644 src/aeonlib/salt/models/serialize/templates/salticam_dither_pattern.xml create mode 100644 tests/salt/models/serialize/test_templates.py diff --git a/src/aeonlib/salt/models/serialize/templates/salticam.xml b/src/aeonlib/salt/models/serialize/templates/salticam.xml new file mode 100644 index 0000000..e93b200 --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/salticam.xml @@ -0,0 +1,38 @@ + + Salticam + {{ salticam.num_cycles }} + + {% for step in salticam.filter_sequence %} + + {{ step.filter }} + + {{ step.exposure_time }} + seconds + + + {% endfor %} + + {% with detector=salticam.detector %} + {% include "salticam_detector.xml" %} + {% endwith %} + 0 + {% if salticam.dither_pattern %} + {% with dither_pattern=salticam.dither_pattern %} + {% include "salticam_dither_pattern.xml" %} + {% endwith %} + {% endif %} + false + {% if salticam.include_flat %} + + + QTH2 + + 1.0 + seconds + + 5 + After science + + + {% endif %} + diff --git a/src/aeonlib/salt/models/serialize/templates/salticam_detector.xml b/src/aeonlib/salt/models/serialize/templates/salticam_detector.xml new file mode 100644 index 0000000..0ae0644 --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/salticam_detector.xml @@ -0,0 +1,9 @@ + + {{ detector.readout_mode }} + {{ detector.num_prebinned_rows }} + {{ detector.num_prebinned_columns }} + Science + {{ detector.gain }} + {{ detector.readout_speed }} + {{ detector.num_exposures }} + diff --git a/src/aeonlib/salt/models/serialize/templates/salticam_dither_pattern.xml b/src/aeonlib/salt/models/serialize/templates/salticam_dither_pattern.xml new file mode 100644 index 0000000..f3640db --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/salticam_dither_pattern.xml @@ -0,0 +1,9 @@ + + {{ dither_pattern.num_columns }} + {{ dither_pattern.num_rows }} + {{ dither_pattern.num_steps }} + + {{ dither_pattern.offset }} + arcseconds + + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py new file mode 100644 index 0000000..6af012e --- /dev/null +++ b/tests/salt/models/serialize/test_templates.py @@ -0,0 +1,35 @@ +import pytest + +from aeonlib.salt.models.util import render_template, validate_xml + + +def test_salticam_detector_template(base_salticam_detector): + """Test that the Salticam detector template generates valid XML.""" + xml = render_template( + "salticam_detector.xml", detector=base_salticam_detector.model_dump() + ) + validate_xml(xml) + assert True + + +@pytest.mark.parametrize("full", [False, True]) +def test_salticam_template(full: bool, base_salticam, base_salticam_dither_pattern): + salticam = base_salticam.model_dump() + salticam["filter_sequence"].append({"filter": "Cousins R", "exposure_time": 42}) + if full: + salticam["dither_pattern"] = base_salticam_dither_pattern.model_dump() + salticam["include_flat"] = True + else: + salticam["dither_pattern"] = None + salticam["include_flat"] = False + xml = render_template("salticam.xml", salticam=salticam) + + if full: + assert "Dither" in xml + assert "Flat" in xml + else: + assert "Dither" not in xml + assert "Flat" not in xml + + validate_xml(xml) + assert True From 3f4b4bede45baea1d5e6b805f5489680d2edea36 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 08:19:15 +0200 Subject: [PATCH 075/171] Add templates for serializing Rss detector setups --- .../serialize/templates/rss_detector.xml | 25 +++++++++++++++++++ tests/salt/models/serialize/test_templates.py | 21 ++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/aeonlib/salt/models/serialize/templates/rss_detector.xml diff --git a/src/aeonlib/salt/models/serialize/templates/rss_detector.xml b/src/aeonlib/salt/models/serialize/templates/rss_detector.xml new file mode 100644 index 0000000..9a56d6b --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/rss_detector.xml @@ -0,0 +1,25 @@ + + {{ detector.readout_mode }} + {{ detector.num_exposures }} + {% if detector.window_height is not none %} + + + {{ detector.window_height }} + arcseconds + + + {% endif %} + 0 + 0 + {{ detector.num_prebinned_rows }} + {{ detector.num_prebinned_columns }} + true + Science + + {{ detector.exposure_time }} + seconds + + {{ detector.gain }} + {{ detector.readout_speed }} + None + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 6af012e..e9623cb 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -33,3 +33,24 @@ def test_salticam_template(full: bool, base_salticam, base_salticam_dither_patte validate_xml(xml) assert True + + +@pytest.mark.parametrize("full", [False, True]) +def test_salticam_detector_template(full: bool, base_rss_detector): + """Test that the RSS detector template generates valid XML.""" + detector = base_rss_detector.model_dump() + + if full: + detector["window_height"] = 45 + else: + detector["window_height"] = None + + xml = render_template("rss_detector.xml", detector=detector) + + if full: + assert "Height" in xml + else: + assert "Height" not in xml + + validate_xml(xml) + assert True From f24a767438b245d20b1341cbdceb49a64472ba02 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 09:06:11 +0200 Subject: [PATCH 076/171] Add a configuration mode field --- src/aeonlib/salt/models/rss_models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 3f9eede..a017ff4 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -104,6 +104,8 @@ class RssImaging(BaseModel): Parameters ---------- + mode + The configuration mode. This must be "imaging". filter The filter to use. This may be one of RSS's own imaging filters or one of the filters used by Salticam. @@ -113,6 +115,7 @@ class RssImaging(BaseModel): Whether a nighttime flat should be taken for the observation. """ + mode: Annotated[Literal["imaging"], LowerCaseValidator] = "imaging" filter: Annotated[ RssImagingFilter | SalticamFilter, LowerCaseValidator, @@ -146,6 +149,8 @@ class RssSpectroscopy(BaseModel): Parameters ---------- + mode + The configuration mode. This must be "spectroscopy". grating The barcode of the grating, such as "pg0900". grating_angle @@ -166,6 +171,7 @@ class RssSpectroscopy(BaseModel): """ + mode: Annotated[Literal["spectroscopy"], LowerCaseValidator] = "spectroscopy" grating: RssGrating grating_angle: Annotated[Angle, GreaterEqual(0 * u.deg), LessEqual(100 * u.deg)] articulation_angle: Angle From df28fb61dc866ced33c3cff3f99bbe1ed261a7f7 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 09:45:53 +0200 Subject: [PATCH 077/171] Refactor tests --- tests/salt/models/serialize/test_templates.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index e9623cb..ced943f 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -1,5 +1,8 @@ import pytest +import astropy.units as u + +from aeonlib.salt.models import SalticamFilterSequenceStep from aeonlib.salt.models.util import render_template, validate_xml @@ -14,15 +17,17 @@ def test_salticam_detector_template(base_salticam_detector): @pytest.mark.parametrize("full", [False, True]) def test_salticam_template(full: bool, base_salticam, base_salticam_dither_pattern): - salticam = base_salticam.model_dump() - salticam["filter_sequence"].append({"filter": "Cousins R", "exposure_time": 42}) + salticam = base_salticam + salticam.filter_sequence.append( + SalticamFilterSequenceStep(filter="Cousins R", exposure_time=42) + ) if full: - salticam["dither_pattern"] = base_salticam_dither_pattern.model_dump() - salticam["include_flat"] = True + salticam.dither_pattern = base_salticam_dither_pattern + salticam.include_flat = True else: - salticam["dither_pattern"] = None - salticam["include_flat"] = False - xml = render_template("salticam.xml", salticam=salticam) + salticam.dither_pattern = None + salticam.include_flat = False + xml = render_template("salticam.xml", salticam=salticam.model_dump()) if full: assert "Dither" in xml @@ -38,14 +43,14 @@ def test_salticam_template(full: bool, base_salticam, base_salticam_dither_patte @pytest.mark.parametrize("full", [False, True]) def test_salticam_detector_template(full: bool, base_rss_detector): """Test that the RSS detector template generates valid XML.""" - detector = base_rss_detector.model_dump() + detector = base_rss_detector if full: - detector["window_height"] = 45 + detector.window_height = 45 * u.deg else: - detector["window_height"] = None + detector.window_height = None - xml = render_template("rss_detector.xml", detector=detector) + xml = render_template("rss_detector.xml", detector=detector.model_dump()) if full: assert "Height" in xml From 34d357186b77567ac1e39dc78c874fba6454db75 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 09:46:56 +0200 Subject: [PATCH 078/171] Ensure the detector window height is an integer --- src/aeonlib/salt/models/serialize/templates/rss_detector.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/serialize/templates/rss_detector.xml b/src/aeonlib/salt/models/serialize/templates/rss_detector.xml index 9a56d6b..1f1173c 100644 --- a/src/aeonlib/salt/models/serialize/templates/rss_detector.xml +++ b/src/aeonlib/salt/models/serialize/templates/rss_detector.xml @@ -4,7 +4,7 @@ {% if detector.window_height is not none %} - {{ detector.window_height }} + {{ detector.window_height|round|int }} arcseconds From 70a70813c839d8e9fa7e537f4d2cf15fd81be1a4 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 09:55:11 +0200 Subject: [PATCH 079/171] Add a template for serializing RSS imaging configurations --- .../models/serialize/templates/rss_imaging.xml | 16 ++++++++++++++++ tests/salt/models/serialize/test_templates.py | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/aeonlib/salt/models/serialize/templates/rss_imaging.xml diff --git a/src/aeonlib/salt/models/serialize/templates/rss_imaging.xml b/src/aeonlib/salt/models/serialize/templates/rss_imaging.xml new file mode 100644 index 0000000..676f40e --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/rss_imaging.xml @@ -0,0 +1,16 @@ + + + + + + {{ configuration.filter }} + {% if configuration.polarimetry %} + + normal + + {% endif %} + + 1000 + microns + + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index ced943f..4940be3 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -59,3 +59,20 @@ def test_salticam_detector_template(full: bool, base_rss_detector): validate_xml(xml) assert True + + +@pytest.mark.parametrize("full", [False, True]) +def test_rss_imaging(full: bool, base_rss_imaging, base_rss_polarimetry): + configuration = base_rss_imaging + + if full: + configuration.polarimetry = base_rss_polarimetry + else: + configuration.polarimetry = None + + xml = render_template("rss_imaging.xml", configuration=configuration.model_dump()) + + if full: + assert "BeamsplitterOrientation" in xml + else: + assert "BeamsplitterOrientation" not in xml From a4ae9c60bbc10aaf1fc4feeb20919aa695dca192 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 12:36:14 +0200 Subject: [PATCH 080/171] Fix the filter serialization --- src/aeonlib/salt/models/rss_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index a017ff4..120ae78 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -127,7 +127,7 @@ class RssImaging(BaseModel): @staticmethod def serialize_filter(value: str) -> str: if value.startswith("pi"): - return value.upper() + return value else: return serialize_salticam_filter(value) From 287266ed870214950fb5caa570431e292bb6eb10 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 14:09:22 +0200 Subject: [PATCH 081/171] Validate RssSpectroscopy instances when assigning fields --- src/aeonlib/salt/models/rss_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 120ae78..6cf9524 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -140,7 +140,7 @@ def serialize_filter(value: str) -> str: ) -class RssSpectroscopy(BaseModel): +class RssSpectroscopy(BaseModel, validate_assignment=True): """ An RSS spectroscopy configuration. From 90295d480b5d0fd59eae73f2994219a84df0beb3 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 14:09:38 +0200 Subject: [PATCH 082/171] Add the articulation station as a computed field --- src/aeonlib/salt/models/rss_models.py | 10 ++++++++++ tests/salt/models/test_rss_models.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 6cf9524..b97e288 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -14,6 +14,7 @@ PlainSerializer, BeforeValidator, AfterValidator, + computed_field, ) from aeonlib.salt.models.types.salticam import serialize_salticam_filter @@ -181,6 +182,15 @@ class RssSpectroscopy(BaseModel, validate_assignment=True): include_arc: bool = True request_spectrophotometric_standard: bool = False + @computed_field + @property + def articulation_station(self) -> int: + """Return the articulation station.""" + degrees = self.articulation_angle.to(u.deg).value + if degrees < 1: + return 0 + return round((degrees - 1) / 0.75) + @field_validator("articulation_angle", mode="after") @classmethod def check_articulation_angle(cls, angle: Angle) -> Angle: diff --git a/tests/salt/models/test_rss_models.py b/tests/salt/models/test_rss_models.py index 580d16a..60a7b0a 100644 --- a/tests/salt/models/test_rss_models.py +++ b/tests/salt/models/test_rss_models.py @@ -57,6 +57,16 @@ def test_articulation_angle_must_have_allowed_value( with expectation: RssSpectroscopy(**spectroscopy) + @pytest.mark.parametrize( + "angle, station", [(0, 0), (1.75, 1), (12.25, 15), (49, 64), (100, 132)] + ) + def test_articulation_station( + self, angle: float, station: int, base_rss_spectroscopy + ): + spectroscopy = base_rss_spectroscopy + spectroscopy.articulation_angle = angle + assert spectroscopy.articulation_station == station + class TestRssPolarimetry: def test_rss_polarimetry(self, base_rss_polarimetry): From 744176c2aea081c712a14b6defe16039af15ae3e Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 14:12:17 +0200 Subject: [PATCH 083/171] Handle RSS filters correctly when generating XML --- .../serialize/templates/rss_imaging.xml | 6 ++- tests/salt/models/serialize/test_templates.py | 47 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/serialize/templates/rss_imaging.xml b/src/aeonlib/salt/models/serialize/templates/rss_imaging.xml index 676f40e..d122faa 100644 --- a/src/aeonlib/salt/models/serialize/templates/rss_imaging.xml +++ b/src/aeonlib/salt/models/serialize/templates/rss_imaging.xml @@ -3,7 +3,11 @@ - {{ configuration.filter }} + {% if configuration.filter.startswith("pi") %} + {{ configuration.filter }} + {% else %} + {{ configuration.filter }} + {% endif %} {% if configuration.polarimetry %} normal diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 4940be3..086c922 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -4,6 +4,7 @@ from aeonlib.salt.models import SalticamFilterSequenceStep from aeonlib.salt.models.util import render_template, validate_xml +from tests.salt.models.conftest import base_rss_longslit_spectroscopy def test_salticam_detector_template(base_salticam_detector): @@ -63,6 +64,7 @@ def test_salticam_detector_template(full: bool, base_rss_detector): @pytest.mark.parametrize("full", [False, True]) def test_rss_imaging(full: bool, base_rss_imaging, base_rss_polarimetry): + """Tests that the RSS imaging template generates valid XML.""" configuration = base_rss_imaging if full: @@ -76,3 +78,48 @@ def test_rss_imaging(full: bool, base_rss_imaging, base_rss_polarimetry): assert "BeamsplitterOrientation" in xml else: assert "BeamsplitterOrientation" not in xml + + validate_xml(xml) + assert True + + +@pytest.mark.parametrize( + "filter_name, expected_element", + [("pi04340", "FilterId"), ("Johnson V", "SalticamFilter")], +) +def test_rss_imaging_filter(filter_name: str, expected_element: str, base_rss_imaging): + """Tests that RSS imaging filters are handled correctly when generating XML.""" + configuration = base_rss_imaging + configuration.filter = filter_name + + xml = render_template("rss_imaging.xml", configuration=configuration) + assert expected_element in xml + + validate_xml(xml) + assert True + + +@pytest.mark.parametrize("full", [False, True]) +def test_rss_longslit_spectroscopy( + full: bool, base_rss_longslit_spectroscopy, base_rss_polarimetry +): + configuration = base_rss_longslit_spectroscopy + + if full: + configuration.polarimetry = base_rss_polarimetry + else: + configuration.polarimetry = None + + xml = render_template( + "rss_spectroscopy.xml", configuration=configuration.model_dump() + ) + + assert "PredefinedMask" in xml + + if full: + assert "BeamsplitterOrientation" in xml + else: + assert "BeamsplitterOrientation" not in xml + + validate_xml(xml) + assert True From c1d6261510f8478e5f0c12b8e9c2069fb5a2cd81 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 14:13:51 +0200 Subject: [PATCH 084/171] Add template for RSS spectroscopy XML --- .../serialize/templates/rss_spectroscopy.xml | 41 +++++++++++++++++++ tests/salt/models/serialize/test_templates.py | 1 - 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/aeonlib/salt/models/serialize/templates/rss_spectroscopy.xml diff --git a/src/aeonlib/salt/models/serialize/templates/rss_spectroscopy.xml b/src/aeonlib/salt/models/serialize/templates/rss_spectroscopy.xml new file mode 100644 index 0000000..b7c683b --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/rss_spectroscopy.xml @@ -0,0 +1,41 @@ + + + {% if configuration.slit %} + + {{ configuration.slit }} + + {% endif %} + {% if configuration.mask %} + + + A{{ configuration.mask.absolute() }} + + + {% endif %} + {% if configuration.slit_mask_ifu %} + + {{ configuration.slit_mask_ifu }} + + {% endif %} + + + + {{ configuration.grating }} + + {{ configuration.grating_angle }} + degrees + + {{ configuration.articulation_station }}_{{ "{:.2f}".format(configuration.articulation_angle) }} + + + {{ configuration.order_blocking_filter }} + {% if configuration.polarimetry %} + + normal + + {% endif %} + + 1000 + microns + + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 086c922..1717b49 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -4,7 +4,6 @@ from aeonlib.salt.models import SalticamFilterSequenceStep from aeonlib.salt.models.util import render_template, validate_xml -from tests.salt.models.conftest import base_rss_longslit_spectroscopy def test_salticam_detector_template(base_salticam_detector): From 53bfb23febcfec6118d4ac51acfb213a71466396 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 14:44:39 +0200 Subject: [PATCH 085/171] Validate all SALT models on field assignment --- src/aeonlib/salt/models/block_models.py | 8 ++++---- src/aeonlib/salt/models/hrs_models.py | 4 ++-- src/aeonlib/salt/models/nirwals_models.py | 4 ++-- src/aeonlib/salt/models/request_models.py | 2 +- src/aeonlib/salt/models/rss_models.py | 10 +++++----- src/aeonlib/salt/models/salticam_models.py | 8 ++++---- src/aeonlib/salt/models/target_models.py | 2 +- src/aeonlib/salt/models/types/quantity.py | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 00d6ed7..df25569 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -28,7 +28,7 @@ from aeonlib.salt.validators import GreaterEqual, LessEqual, check_in_visibility_range -class Block(BaseModel): +class Block(BaseModel, validate_assignment=True): """ A block for SALT. @@ -132,7 +132,7 @@ def check_max_num_visits_is_at_least_num_visits(self) -> Self: return self -class Constraints(BaseModel): +class Constraints(BaseModel, validate_assignment=True): """ Observing constraints. @@ -171,7 +171,7 @@ class Constraints(BaseModel): max_seeing: PositiveFloat -class Acquisition(BaseModel): +class Acquisition(BaseModel, validate_assignment=True): """ An acquisition. @@ -210,7 +210,7 @@ class Acquisition(BaseModel): include_focused_image: bool = False -class ReferenceStar(BaseModel): +class ReferenceStar(BaseModel, validate_assignment=True): """ A reference star on which to acquire. diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py index 74d1381..98c05d8 100644 --- a/src/aeonlib/salt/models/hrs_models.py +++ b/src/aeonlib/salt/models/hrs_models.py @@ -11,7 +11,7 @@ from aeonlib.types import Angle -class Hrs(BaseModel): +class Hrs(BaseModel, validate_assignment=True): """ An HRS setup. @@ -80,7 +80,7 @@ def serialize_prv_calibration(value: str | None) -> str | None: raise ValueError(f"Precision radial velocity cannot be serialized: {value}") -class HrsDetector(BaseModel): +class HrsDetector(BaseModel, validate_assignment=True): """ An HRS detector setup. diff --git a/src/aeonlib/salt/models/nirwals_models.py b/src/aeonlib/salt/models/nirwals_models.py index 386a6eb..913a3ff 100644 --- a/src/aeonlib/salt/models/nirwals_models.py +++ b/src/aeonlib/salt/models/nirwals_models.py @@ -31,7 +31,7 @@ from aeonlib.types import Angle -class Nirwals(BaseModel): +class Nirwals(BaseModel, validate_assignment=True): """ A NIRWALS configuration. @@ -95,7 +95,7 @@ def check_articulation_angle(cls, angle: Angle) -> Angle: return angle -class NirwalsDitherPatternStep(BaseModel): +class NirwalsDitherPatternStep(BaseModel, validate_assignment=True): """ A step in a NIRWALS dither pattern. diff --git a/src/aeonlib/salt/models/request_models.py b/src/aeonlib/salt/models/request_models.py index a3131cd..4eff331 100644 --- a/src/aeonlib/salt/models/request_models.py +++ b/src/aeonlib/salt/models/request_models.py @@ -6,7 +6,7 @@ from pydantic import BaseModel -class Request(BaseModel): +class Request(BaseModel, validate_assignment=True): """ An observation request for SALT. diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index b97e288..55283b1 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -44,7 +44,7 @@ from aeonlib.salt.validators import GreaterEqual, GreaterThan, LessEqual -class Rss(BaseModel): +class Rss(BaseModel, validate_assignment=True): """ An RSS configuration. @@ -99,7 +99,7 @@ class Rss(BaseModel): dither_pattern: RssDitherPattern | None -class RssImaging(BaseModel): +class RssImaging(BaseModel, validate_assignment=True): """ An RSS imaging configuration. @@ -263,7 +263,7 @@ class RssSlitMaskIFUSpectroscopy(RssSpectroscopy): slit_mask_ifu: Annotated[RssSlitMaskIFU, LowerCaseValidator, UpperCaseSerializer] -class RssPolarimetry(BaseModel): +class RssPolarimetry(BaseModel, validate_assignment=True): """ An RSS polarimetry setup. @@ -359,7 +359,7 @@ def _check_angle_values(value: _WavePlatePattern) -> _WavePlatePattern: RssPolarimetry._check_pattern_step(step) -class RssDetector(BaseModel): +class RssDetector(BaseModel, validate_assignment=True): """ An Rss detector setup. @@ -398,7 +398,7 @@ class RssDetector(BaseModel): ] = None -class RssDitherPattern(BaseModel): +class RssDitherPattern(BaseModel, validate_assignment=True): """ A dither pattern for RSS. diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index ae8e927..c5f1b02 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -17,7 +17,7 @@ from aeonlib.salt.validators import GreaterEqual, LessEqual -class Salticam(BaseModel): +class Salticam(BaseModel, validate_assignment=True): """ A Salticam instrument configuration. @@ -58,7 +58,7 @@ class Salticam(BaseModel): include_flat: bool -class SalticamFilterSequenceStep(BaseModel): +class SalticamFilterSequenceStep(BaseModel, validate_assignment=True): """ A step in a filter sequence. @@ -75,7 +75,7 @@ class SalticamFilterSequenceStep(BaseModel): exposure_time: PositiveDuration -class SalticamDetector(BaseModel): +class SalticamDetector(BaseModel, validate_assignment=True): """ A Salticam detector setup. @@ -95,7 +95,7 @@ class SalticamDetector(BaseModel): num_prebinned_columns: Annotated[int, GreaterEqual(1), LessEqual(9)] -class SalticamDitherPattern(BaseModel): +class SalticamDitherPattern(BaseModel, validate_assignment=True): """ A dither pattern for Salticam. diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py index 2b4c3de..ca3cb30 100644 --- a/src/aeonlib/salt/models/target_models.py +++ b/src/aeonlib/salt/models/target_models.py @@ -38,7 +38,7 @@ def check_declination_viewable(cls, value: astropy.coordinates.Angle): return check_in_visibility_range(value) -class MagnitudeRange(BaseModel): +class MagnitudeRange(BaseModel, validate_assignment=True): """ A magnitude range. diff --git a/src/aeonlib/salt/models/types/quantity.py b/src/aeonlib/salt/models/types/quantity.py index bb77cce..145dae6 100644 --- a/src/aeonlib/salt/models/types/quantity.py +++ b/src/aeonlib/salt/models/types/quantity.py @@ -34,7 +34,7 @@ class AstropyQuantityTypeAnnotation: ``` from pydantic import BaseModel - class MovingObject(BaseModel): + class MovingObject(BaseModel, validate_assignment=True): proper_motion: ProperMotion # Create the same object in three different ways. From 99a781c245d6f243ec65ad54a8bdc8a841fdeef7 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 15:19:33 +0200 Subject: [PATCH 086/171] Handle null values --- src/aeonlib/salt/validators.py | 8 ++++---- tests/salt/test_validators.py | 27 +++++++++++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/aeonlib/salt/validators.py b/src/aeonlib/salt/validators.py index 5d42c44..e7e3fdf 100644 --- a/src/aeonlib/salt/validators.py +++ b/src/aeonlib/salt/validators.py @@ -8,25 +8,25 @@ def _check_gt(a: Any, b: Any) -> Any: - if a <= b: + if a is not None and b is not None and a <= b: raise ValueError(f"{a} is not greater than to {b}.") return a def _check_ge(a: Any, b: Any) -> Any: - if a < b: + if a is not None and b is not None and a < b: raise ValueError(f"{a} is not greater than or equal to {b}.") return a def _check_lt(a: Any, b: Any) -> None: - if a >= b: + if a is not None and b is not None and a >= b: raise ValueError(f"{a} is not less than to {b}.") return a def _check_le(a: Any, b: Any) -> None: - if a > b: + if a is not None and b is not None and a > b: raise ValueError(f"{a} is not less than or equal to {b}.") return a diff --git a/tests/salt/test_validators.py b/tests/salt/test_validators.py index d87e175..78b5c12 100644 --- a/tests/salt/test_validators.py +++ b/tests/salt/test_validators.py @@ -8,19 +8,19 @@ class GreaterThanModel(BaseModel): - a: Annotated[int, GreaterThan(4)] + a: Annotated[int | None, GreaterThan(4)] class GreaterEqualModel(BaseModel): - a: Annotated[int, GreaterEqual(4)] + a: Annotated[int | None, GreaterEqual(4)] class LessThanModel(BaseModel): - a: Annotated[int, LessThan(4)] + a: Annotated[int | None, LessThan(4)] class LessEqualModel(BaseModel): - a: Annotated[int, LessEqual(4)] + a: Annotated[int | None, LessEqual(4)] class TestValidators: @@ -30,6 +30,7 @@ class TestValidators: (3, pytest.raises(ValidationError)), (4, pytest.raises(ValidationError)), (5, nullcontext()), + (None, nullcontext()), ], ) def test_greater_than(self, a, expectation): @@ -43,7 +44,12 @@ def test_greater_than_does_not_change_field_value(self): @pytest.mark.parametrize( "a, expectation", - [(3, pytest.raises(ValidationError)), (4, nullcontext()), (5, nullcontext())], + [ + (3, pytest.raises(ValidationError)), + (4, nullcontext()), + (5, nullcontext()), + (None, nullcontext()), + ], ) def test_greater_equal(self, a, expectation): """Test that the GreaterEqual validator validates correctly.""" @@ -53,6 +59,7 @@ def test_greater_equal(self, a, expectation): def test_greater_equal_does_not_change_field_value(self): """Test that the field value is not changed by the GreaterEqual validator.""" assert GreaterEqualModel(a=7).a == 7 + assert GreaterEqualModel(a=None).a is None @pytest.mark.parametrize( "a, expectation", @@ -60,6 +67,7 @@ def test_greater_equal_does_not_change_field_value(self): (3, nullcontext()), (4, pytest.raises(ValidationError)), (5, pytest.raises(ValidationError)), + (None, nullcontext()), ], ) def test_less_than(self, a, expectation): @@ -70,10 +78,16 @@ def test_less_than(self, a, expectation): def test_less_than_does_not_change_field_value(self): """Test that the field value is not changed by the LessThan validator.""" assert LessThanModel(a=2).a == 2 + assert LessThanModel(a=None).a is None @pytest.mark.parametrize( "a, expectation", - [(3, nullcontext()), (4, nullcontext()), (5, pytest.raises(ValidationError))], + [ + (3, nullcontext()), + (4, nullcontext()), + (5, pytest.raises(ValidationError)), + (None, nullcontext()), + ], ) def test_less_equal(self, a, expectation): """Test that the LessEqual validator validates correctly.""" @@ -83,3 +97,4 @@ def test_less_equal(self, a, expectation): def test_less_equal_does_not_change_field_value(self): """Test that the field value is not changed by the LessEqual validator.""" assert LessEqualModel(a=2).a == 2 + assert LessEqualModel(a=None).a is None From 55df4849878c5d50b6cea42cd2db228d8b177aa6 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 17:06:18 +0200 Subject: [PATCH 087/171] Fix the detector window height value in the XML --- src/aeonlib/salt/models/serialize/templates/rss_detector.xml | 2 +- tests/salt/models/serialize/test_templates.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aeonlib/salt/models/serialize/templates/rss_detector.xml b/src/aeonlib/salt/models/serialize/templates/rss_detector.xml index 1f1173c..c194750 100644 --- a/src/aeonlib/salt/models/serialize/templates/rss_detector.xml +++ b/src/aeonlib/salt/models/serialize/templates/rss_detector.xml @@ -4,7 +4,7 @@ {% if detector.window_height is not none %} - {{ detector.window_height|round|int }} + {{ (3600 *detector.window_height)|round|int }} arcseconds diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 1717b49..9a398f3 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -41,12 +41,12 @@ def test_salticam_template(full: bool, base_salticam, base_salticam_dither_patte @pytest.mark.parametrize("full", [False, True]) -def test_salticam_detector_template(full: bool, base_rss_detector): +def test_rss_detector_template(full: bool, base_rss_detector): """Test that the RSS detector template generates valid XML.""" detector = base_rss_detector if full: - detector.window_height = 45 * u.deg + detector.window_height = 45 * u.arcsec else: detector.window_height = None From 7930aa750a4bf98c66c6d6e2d5295bfe88c1b407 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 17:13:26 +0200 Subject: [PATCH 088/171] Fix a unit test --- tests/salt/models/serialize/test_templates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 9a398f3..e368ce4 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -91,7 +91,7 @@ def test_rss_imaging_filter(filter_name: str, expected_element: str, base_rss_im configuration = base_rss_imaging configuration.filter = filter_name - xml = render_template("rss_imaging.xml", configuration=configuration) + xml = render_template("rss_imaging.xml", configuration=configuration.model_dump()) assert expected_element in xml validate_xml(xml) From 45ad83d68328c421515ab931092270e8b9f1b8cf Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 17:13:49 +0200 Subject: [PATCH 089/171] Make the Salticam dither pattern optional --- src/aeonlib/salt/models/salticam_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index c5f1b02..37a245a 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -54,7 +54,7 @@ class Salticam(BaseModel, validate_assignment=True): num_cycles: PositiveInt = 1 filter_sequence: Annotated[list[SalticamFilterSequenceStep], MinLen(1)] detector: SalticamDetector - dither_pattern: SalticamDitherPattern = None + dither_pattern: SalticamDitherPattern | None = None include_flat: bool From d381b226c6c178eaf41c908cd8a82742456e6367 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 17:20:29 +0200 Subject: [PATCH 090/171] Add tests for generating XML for RSS spectroscopy setups --- tests/salt/models/serialize/test_templates.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index e368ce4..44dfcfc 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -122,3 +122,56 @@ def test_rss_longslit_spectroscopy( validate_xml(xml) assert True + + +@pytest.mark.parametrize("full", [False, True]) +def test_rss_mos_spectroscopy( + full: bool, base_rss_multi_object_spectroscopy, base_rss_polarimetry +): + configuration = base_rss_multi_object_spectroscopy + + if full: + configuration.polarimetry = base_rss_polarimetry + else: + configuration.polarimetry = None + + xml = render_template( + "rss_spectroscopy.xml", configuration=configuration.model_dump() + ) + + assert "MOS" in xml + assert "Path" in xml + + if full: + assert "BeamsplitterOrientation" in xml + else: + assert "BeamsplitterOrientation" not in xml + + validate_xml(xml) + assert True + + +@pytest.mark.parametrize("full", [False, True]) +def test_rss_slit_mask_ifu_spectroscopy( + full: bool, base_rss_slit_mask_ifu_spectroscopy, base_rss_polarimetry +): + configuration = base_rss_slit_mask_ifu_spectroscopy + + if full: + configuration.polarimetry = base_rss_polarimetry + else: + configuration.polarimetry = None + + xml = render_template( + "rss_spectroscopy.xml", configuration=configuration.model_dump() + ) + + assert "SMI" in xml + + if full: + assert "BeamsplitterOrientation" in xml + else: + assert "BeamsplitterOrientation" not in xml + + validate_xml(xml) + assert True From 16f8dba4f4316754fb60a73062fd6180f085a8de Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 17:26:32 +0200 Subject: [PATCH 091/171] Group unit tests in classes --- tests/salt/models/serialize/test_templates.py | 340 +++++++++--------- 1 file changed, 172 insertions(+), 168 deletions(-) diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 44dfcfc..6aa43fc 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -6,172 +6,176 @@ from aeonlib.salt.models.util import render_template, validate_xml -def test_salticam_detector_template(base_salticam_detector): - """Test that the Salticam detector template generates valid XML.""" - xml = render_template( - "salticam_detector.xml", detector=base_salticam_detector.model_dump() +class TestSalticamTemplates: + def test_salticam_detector_template(self, base_salticam_detector): + """Test that the Salticam detector template generates valid XML.""" + xml = render_template( + "salticam_detector.xml", detector=base_salticam_detector.model_dump() + ) + validate_xml(xml) + assert True + + @pytest.mark.parametrize("full", [False, True]) + def test_salticam_template( + self, full: bool, base_salticam, base_salticam_dither_pattern + ): + salticam = base_salticam + salticam.filter_sequence.append( + SalticamFilterSequenceStep(filter="Cousins R", exposure_time=42) + ) + if full: + salticam.dither_pattern = base_salticam_dither_pattern + salticam.include_flat = True + else: + salticam.dither_pattern = None + salticam.include_flat = False + xml = render_template("salticam.xml", salticam=salticam.model_dump()) + + if full: + assert "Dither" in xml + assert "Flat" in xml + else: + assert "Dither" not in xml + assert "Flat" not in xml + + validate_xml(xml) + assert True + + +class TestRssTemplates: + @pytest.mark.parametrize("full", [False, True]) + def test_rss_detector_template(self, full: bool, base_rss_detector): + """Test that the RSS detector template generates valid XML.""" + detector = base_rss_detector + + if full: + detector.window_height = 45 * u.arcsec + else: + detector.window_height = None + + xml = render_template("rss_detector.xml", detector=detector.model_dump()) + + if full: + assert "Height" in xml + else: + assert "Height" not in xml + + validate_xml(xml) + assert True + + @pytest.mark.parametrize("full", [False, True]) + def test_rss_imaging(self, full: bool, base_rss_imaging, base_rss_polarimetry): + """Tests that the RSS imaging template generates valid XML.""" + configuration = base_rss_imaging + + if full: + configuration.polarimetry = base_rss_polarimetry + else: + configuration.polarimetry = None + + xml = render_template( + "rss_imaging.xml", configuration=configuration.model_dump() + ) + + if full: + assert "BeamsplitterOrientation" in xml + else: + assert "BeamsplitterOrientation" not in xml + + validate_xml(xml) + assert True + + @pytest.mark.parametrize( + "filter_name, expected_element", + [("pi04340", "FilterId"), ("Johnson V", "SalticamFilter")], ) - validate_xml(xml) - assert True - - -@pytest.mark.parametrize("full", [False, True]) -def test_salticam_template(full: bool, base_salticam, base_salticam_dither_pattern): - salticam = base_salticam - salticam.filter_sequence.append( - SalticamFilterSequenceStep(filter="Cousins R", exposure_time=42) - ) - if full: - salticam.dither_pattern = base_salticam_dither_pattern - salticam.include_flat = True - else: - salticam.dither_pattern = None - salticam.include_flat = False - xml = render_template("salticam.xml", salticam=salticam.model_dump()) - - if full: - assert "Dither" in xml - assert "Flat" in xml - else: - assert "Dither" not in xml - assert "Flat" not in xml - - validate_xml(xml) - assert True - - -@pytest.mark.parametrize("full", [False, True]) -def test_rss_detector_template(full: bool, base_rss_detector): - """Test that the RSS detector template generates valid XML.""" - detector = base_rss_detector - - if full: - detector.window_height = 45 * u.arcsec - else: - detector.window_height = None - - xml = render_template("rss_detector.xml", detector=detector.model_dump()) - - if full: - assert "Height" in xml - else: - assert "Height" not in xml - - validate_xml(xml) - assert True - - -@pytest.mark.parametrize("full", [False, True]) -def test_rss_imaging(full: bool, base_rss_imaging, base_rss_polarimetry): - """Tests that the RSS imaging template generates valid XML.""" - configuration = base_rss_imaging - - if full: - configuration.polarimetry = base_rss_polarimetry - else: - configuration.polarimetry = None - - xml = render_template("rss_imaging.xml", configuration=configuration.model_dump()) - - if full: - assert "BeamsplitterOrientation" in xml - else: - assert "BeamsplitterOrientation" not in xml - - validate_xml(xml) - assert True - - -@pytest.mark.parametrize( - "filter_name, expected_element", - [("pi04340", "FilterId"), ("Johnson V", "SalticamFilter")], -) -def test_rss_imaging_filter(filter_name: str, expected_element: str, base_rss_imaging): - """Tests that RSS imaging filters are handled correctly when generating XML.""" - configuration = base_rss_imaging - configuration.filter = filter_name - - xml = render_template("rss_imaging.xml", configuration=configuration.model_dump()) - assert expected_element in xml - - validate_xml(xml) - assert True - - -@pytest.mark.parametrize("full", [False, True]) -def test_rss_longslit_spectroscopy( - full: bool, base_rss_longslit_spectroscopy, base_rss_polarimetry -): - configuration = base_rss_longslit_spectroscopy - - if full: - configuration.polarimetry = base_rss_polarimetry - else: - configuration.polarimetry = None - - xml = render_template( - "rss_spectroscopy.xml", configuration=configuration.model_dump() - ) - - assert "PredefinedMask" in xml - - if full: - assert "BeamsplitterOrientation" in xml - else: - assert "BeamsplitterOrientation" not in xml - - validate_xml(xml) - assert True - - -@pytest.mark.parametrize("full", [False, True]) -def test_rss_mos_spectroscopy( - full: bool, base_rss_multi_object_spectroscopy, base_rss_polarimetry -): - configuration = base_rss_multi_object_spectroscopy - - if full: - configuration.polarimetry = base_rss_polarimetry - else: - configuration.polarimetry = None - - xml = render_template( - "rss_spectroscopy.xml", configuration=configuration.model_dump() - ) - - assert "MOS" in xml - assert "Path" in xml - - if full: - assert "BeamsplitterOrientation" in xml - else: - assert "BeamsplitterOrientation" not in xml - - validate_xml(xml) - assert True - - -@pytest.mark.parametrize("full", [False, True]) -def test_rss_slit_mask_ifu_spectroscopy( - full: bool, base_rss_slit_mask_ifu_spectroscopy, base_rss_polarimetry -): - configuration = base_rss_slit_mask_ifu_spectroscopy - - if full: - configuration.polarimetry = base_rss_polarimetry - else: - configuration.polarimetry = None - - xml = render_template( - "rss_spectroscopy.xml", configuration=configuration.model_dump() - ) - - assert "SMI" in xml - - if full: - assert "BeamsplitterOrientation" in xml - else: - assert "BeamsplitterOrientation" not in xml - - validate_xml(xml) - assert True + def test_rss_imaging_filter( + self, filter_name: str, expected_element: str, base_rss_imaging + ): + """Tests that RSS imaging filters are handled correctly when generating XML.""" + configuration = base_rss_imaging + configuration.filter = filter_name + + xml = render_template( + "rss_imaging.xml", configuration=configuration.model_dump() + ) + assert expected_element in xml + + validate_xml(xml) + assert True + + @pytest.mark.parametrize("full", [False, True]) + def test_rss_longslit_spectroscopy( + self, full: bool, base_rss_longslit_spectroscopy, base_rss_polarimetry + ): + configuration = base_rss_longslit_spectroscopy + + if full: + configuration.polarimetry = base_rss_polarimetry + else: + configuration.polarimetry = None + + xml = render_template( + "rss_spectroscopy.xml", configuration=configuration.model_dump() + ) + + assert "PredefinedMask" in xml + + if full: + assert "BeamsplitterOrientation" in xml + else: + assert "BeamsplitterOrientation" not in xml + + validate_xml(xml) + assert True + + @pytest.mark.parametrize("full", [False, True]) + def test_rss_mos_spectroscopy( + self, full: bool, base_rss_multi_object_spectroscopy, base_rss_polarimetry + ): + configuration = base_rss_multi_object_spectroscopy + + if full: + configuration.polarimetry = base_rss_polarimetry + else: + configuration.polarimetry = None + + xml = render_template( + "rss_spectroscopy.xml", configuration=configuration.model_dump() + ) + + assert "MOS" in xml + assert "Path" in xml + + if full: + assert "BeamsplitterOrientation" in xml + else: + assert "BeamsplitterOrientation" not in xml + + validate_xml(xml) + assert True + + @pytest.mark.parametrize("full", [False, True]) + def test_rss_slit_mask_ifu_spectroscopy( + self, full: bool, base_rss_slit_mask_ifu_spectroscopy, base_rss_polarimetry + ): + configuration = base_rss_slit_mask_ifu_spectroscopy + + if full: + configuration.polarimetry = base_rss_polarimetry + else: + configuration.polarimetry = None + + xml = render_template( + "rss_spectroscopy.xml", configuration=configuration.model_dump() + ) + + assert "SMI" in xml + + if full: + assert "BeamsplitterOrientation" in xml + else: + assert "BeamsplitterOrientation" not in xml + + validate_xml(xml) + assert True From 9a50ea8da0a702b6034a64697ed18d0baafed360 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 18:29:31 +0200 Subject: [PATCH 092/171] Add test for generating the XML for a Salticam dither pattern --- tests/salt/models/serialize/test_templates.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 6aa43fc..fdb3d6e 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -15,6 +15,16 @@ def test_salticam_detector_template(self, base_salticam_detector): validate_xml(xml) assert True + def test_salticam_dithering_pattern(self, base_salticam_dither_pattern): + """Test that the Salticam dither pattern template generates valid XML.""" + dither_pattern = base_salticam_dither_pattern + + xml = render_template( + "salticam_dither_pattern.xml", dither_pattern=dither_pattern.model_dump() + ) + + validate_xml(xml) + @pytest.mark.parametrize("full", [False, True]) def test_salticam_template( self, full: bool, base_salticam, base_salticam_dither_pattern From 02b1627a226fd7becc260ef6fa07be38eacc168e Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 18:30:46 +0200 Subject: [PATCH 093/171] Add a template for generating the XML for an RSS dither pattern --- .../models/serialize/templates/rss_dither_pattern.xml | 9 +++++++++ tests/salt/models/serialize/test_templates.py | 10 ++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/aeonlib/salt/models/serialize/templates/rss_dither_pattern.xml diff --git a/src/aeonlib/salt/models/serialize/templates/rss_dither_pattern.xml b/src/aeonlib/salt/models/serialize/templates/rss_dither_pattern.xml new file mode 100644 index 0000000..f3640db --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/rss_dither_pattern.xml @@ -0,0 +1,9 @@ + + {{ dither_pattern.num_columns }} + {{ dither_pattern.num_rows }} + {{ dither_pattern.num_steps }} + + {{ dither_pattern.offset }} + arcseconds + + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index fdb3d6e..5d73543 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -189,3 +189,13 @@ def test_rss_slit_mask_ifu_spectroscopy( validate_xml(xml) assert True + + def test_rss_dithering_pattern(self, base_rss_dither_pattern): + dither_pattern = base_rss_dither_pattern + """Test that the template for RSS dither patterns generates valid XML.""" + + xml = render_template( + "rss_dither_pattern.xml", dither_pattern=dither_pattern.model_dump() + ) + + validate_xml(xml) From c0ef922e8349d1cc3bd46f4d461d5082bac3080a Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 18:31:33 +0200 Subject: [PATCH 094/171] Update docstrings --- tests/salt/models/serialize/test_templates.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 5d73543..a8bc061 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -29,6 +29,7 @@ def test_salticam_dithering_pattern(self, base_salticam_dither_pattern): def test_salticam_template( self, full: bool, base_salticam, base_salticam_dither_pattern ): + """Test that the Salticam template generates valid XML.""" salticam = base_salticam salticam.filter_sequence.append( SalticamFilterSequenceStep(filter="Cousins R", exposure_time=42) @@ -118,6 +119,7 @@ def test_rss_imaging_filter( def test_rss_longslit_spectroscopy( self, full: bool, base_rss_longslit_spectroscopy, base_rss_polarimetry ): + """Test that the template for RSS longslit spectroscopy generates valid XML.""" configuration = base_rss_longslit_spectroscopy if full: @@ -143,6 +145,9 @@ def test_rss_longslit_spectroscopy( def test_rss_mos_spectroscopy( self, full: bool, base_rss_multi_object_spectroscopy, base_rss_polarimetry ): + """ + Test that the template for RSS multi-object spectroscopy generates valid XML. + """ configuration = base_rss_multi_object_spectroscopy if full: @@ -169,6 +174,9 @@ def test_rss_mos_spectroscopy( def test_rss_slit_mask_ifu_spectroscopy( self, full: bool, base_rss_slit_mask_ifu_spectroscopy, base_rss_polarimetry ): + """ + Test that the template for RSS slit mask IFY spectroscopy generates valid XML. + """ configuration = base_rss_slit_mask_ifu_spectroscopy if full: From 0dd758aba1a28640ece7402180c1a86b04905578 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 18:33:13 +0200 Subject: [PATCH 095/171] Update the proposal schema --- src/aeonlib/salt/models/proposal.xsd | 376 +++++++++++++-------------- 1 file changed, 183 insertions(+), 193 deletions(-) diff --git a/src/aeonlib/salt/models/proposal.xsd b/src/aeonlib/salt/models/proposal.xsd index c051f22..69f06f5 100644 --- a/src/aeonlib/salt/models/proposal.xsd +++ b/src/aeonlib/salt/models/proposal.xsd @@ -1,4 +1,6 @@ - + + @@ -42,7 +44,6 @@ - @@ -89,7 +90,8 @@ - + @@ -98,7 +100,7 @@ - + @@ -107,7 +109,7 @@ - + @@ -200,6 +202,27 @@ + + + + + + + + The percentage of time requested from a partner. + + + + + + + + + + + + @@ -224,7 +247,6 @@ - @@ -263,7 +285,6 @@ - @@ -294,7 +315,7 @@ - + @@ -739,7 +760,7 @@ - + @@ -1083,6 +1104,7 @@ + @@ -1108,7 +1130,7 @@ - + @@ -1116,67 +1138,10 @@ - - - - - - The SALT partners associated with this proposal. - - - - - - - - - - - - The percentage of the total required time to be charged to this SALT - partner. - - - - - - - - - - The semester. - - 1: 1 May to 31 October - 2: 1 November to 30 April - - - - - - - - - - - - - - - - - - - - - - - - - - + - The name of the SALT partner. + Enumeration of all thw SALT partner names. @@ -1696,7 +1661,8 @@ - + @@ -1895,6 +1861,13 @@ + + + + + + + @@ -2605,6 +2578,13 @@ + + + + + + + @@ -2649,6 +2629,13 @@ + + + + + + + @@ -3396,7 +3383,8 @@ - This states whether the NIRWALS setup is a (nighttime) calibration. + This states whether the NIRWALS setup is a (nighttime) calibration. + @@ -3442,7 +3430,10 @@ - How many reads are done for each sample. For Fowler this is how many reads are done at the beginning and at the end. For Up-the-Ramp Group this is how many reads you do at each sample (group) up the ramp. + How many reads are done for each sample. For Fowler this is how many reads are + done at the beginning and at the end. For Up-the-Ramp Group this is how many reads you do at + each sample (group) up the ramp. + @@ -4803,242 +4794,241 @@ - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + From 6e55cb8d41f25f8946748e51af937b8e725e2a1a Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 9 Feb 2026 18:33:39 +0200 Subject: [PATCH 096/171] Use default calibration flats --- .../salt/models/serialize/templates/salticam.xml | 10 +--------- tests/salt/models/serialize/test_templates.py | 4 ++-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/aeonlib/salt/models/serialize/templates/salticam.xml b/src/aeonlib/salt/models/serialize/templates/salticam.xml index e93b200..d7de1bc 100644 --- a/src/aeonlib/salt/models/serialize/templates/salticam.xml +++ b/src/aeonlib/salt/models/serialize/templates/salticam.xml @@ -24,15 +24,7 @@ false {% if salticam.include_flat %} - - QTH2 - - 1.0 - seconds - - 5 - After science - + {% endif %} diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index a8bc061..926cca1 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -44,10 +44,10 @@ def test_salticam_template( if full: assert "Dither" in xml - assert "Flat" in xml + assert "SalticamDefaultCalibrationFlat" in xml else: assert "Dither" not in xml - assert "Flat" not in xml + assert "SalticamDefaultCalibrationFlat" not in xml validate_xml(xml) assert True From 683ce712324f8a516a872ca8051c5aa6edc6784f Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 09:35:07 +0200 Subject: [PATCH 097/171] Add a Jinja filter for wave plate stations --- src/aeonlib/salt/models/util.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index 4f86ea3..318dcc0 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -101,6 +101,13 @@ def _load_schema(): _schema = etree.XMLSchema(schema_doc) +def _wave_plate_station(angle): + if angle < 1e-5: + return "0_0" + else: + return f"{(angle / 11.25):.0f}_{angle:.2f}" + + def render_template( template_path: str, loader: BaseLoader | None = None, **kwargs ) -> str: @@ -132,6 +139,7 @@ def render_template( loader = PackageLoader("aeonlib.salt.models.serialize") env = Environment(loader=loader, autoescape=select_autoescape()) + env.filters["wave_plate_station"] = _wave_plate_station template = env.get_template(template_path) return template.render(**kwargs) From cced7b483889f36071eaca31cfd58920ef4d46c2 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 09:35:18 +0200 Subject: [PATCH 098/171] Add a template for generating RSS setups --- .../salt/models/serialize/templates/rss.xml | 50 +++++++++++++++++ tests/salt/models/serialize/test_templates.py | 54 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 src/aeonlib/salt/models/serialize/templates/rss.xml diff --git a/src/aeonlib/salt/models/serialize/templates/rss.xml b/src/aeonlib/salt/models/serialize/templates/rss.xml new file mode 100644 index 0000000..04efdc9 --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/rss.xml @@ -0,0 +1,50 @@ + + RSS + {% if rss.configuration.polarimetry %} + + + {% for step in rss.configuration.polarimetry.wave_plate_pattern %} + + {% if step[0] is not none %} + {{ step[0]|wave_plate_station }} + {% endif %} + {% if step[1] is not none %} + {{ step[1]|wave_plate_station }} + {% endif %} + + {% endfor %} + + + {% endif %} + {% with configuration=rss.configuration %} + {% if configuration.mode == "imaging" %} + {% include "rss_imaging.xml" %} + {% endif %} + {% if configuration.mode == "spectroscopy" %} + {% include "rss_spectroscopy.xml" %} + {% endif %} + {% endwith %} + {% with detector=rss.detector %} + {% include "rss_detector.xml" %} + {% endwith %} + 0 + {{ rss.num_cycles }} + {% if rss.configuration.include_flat %} + + + + {% endif %} + {% if rss.configuration.include_arc %} + + + + {% endif %} + {% if rss.configuration.request_spectrophotometric_standard %} + + + Spectrophotometric + + + {% endif %} + false + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 926cca1..6a4c4f1 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -207,3 +207,57 @@ def test_rss_dithering_pattern(self, base_rss_dither_pattern): ) validate_xml(xml) + + @pytest.mark.parametrize("full", [False, True]) + def test_rss( + self, full: bool, base_rss, base_rss_polarimetry, base_rss_longslit_spectroscopy + ): + """Test that the RSS template generates valid XML.""" + rss = base_rss + rss.configuration = base_rss_longslit_spectroscopy + + if full: + rss.configuration.polarimetry.wave_plate_pattern = ( + "circular" # base_rss_polarimetry + ) + rss.configuration.include_flat = True + rss.configuration.include_arc = True + rss.configuration.request_spectrophotometric_standard = True + else: + rss.configuration.polarimetry = None + rss.configuration.include_flat = False + rss.configuration.include_arc = False + rss.configuration.request_spectrophotometric_standard = False + + xml = render_template("rss.xml", rss=rss.model_dump()) + + if full: + assert "RssProcedure" in xml + assert "WaveplatePattern" in xml + assert "RssDefaultCalibrationFlat" in xml + assert "RssDefaultArc" in xml + assert "RssStandard" in xml + else: + assert "RssProcedure" not in xml + assert "WaveplatePattern" not in xml + assert "RssDefaultCalibrationFlat" not in xml + assert "RssDefaultArc" not in xml + assert "RssStandard" not in xml + + validate_xml(xml) + assert True + + def test_rss_wave_plate_pattern_step_values( + self, base_rss, base_rss_longslit_spectroscopy, base_rss_polarimetry + ): + """Test that the wave plate pattern step values are correct.""" + rss = base_rss + rss.configuration = base_rss_longslit_spectroscopy + rss.configuration.polarimetry = base_rss_polarimetry + rss.configuration.polarimetry.wave_plate_pattern = "circular" + + xml = render_template("rss.xml", rss=rss.model_dump()) + + assert "0_0" in xml + assert "4_45.00" in xml + assert "28_315.00" in xml From 229e2a69190ee564ee591ed56dde4629778f40af Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 09:37:28 +0200 Subject: [PATCH 099/171] Prettify the generated XML a bit --- src/aeonlib/salt/models/util.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index 318dcc0..4a644d4 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -139,6 +139,8 @@ def render_template( loader = PackageLoader("aeonlib.salt.models.serialize") env = Environment(loader=loader, autoescape=select_autoescape()) + env.trim_blocks = True + env.lstrip_blocks = True env.filters["wave_plate_station"] = _wave_plate_station template = env.get_template(template_path) return template.render(**kwargs) From 30d1484ccef03cfe6eb13e22243101d9d6a4cd4c Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 09:54:35 +0200 Subject: [PATCH 100/171] Mark the number of groups as a computed field --- src/aeonlib/salt/models/nirwals_models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/nirwals_models.py b/src/aeonlib/salt/models/nirwals_models.py index 913a3ff..d4dd7ae 100644 --- a/src/aeonlib/salt/models/nirwals_models.py +++ b/src/aeonlib/salt/models/nirwals_models.py @@ -10,6 +10,7 @@ field_validator, PlainSerializer, BeforeValidator, + computed_field, ) from aeonlib.salt.models.types import ( @@ -156,8 +157,9 @@ class NirwalsDitherPatternStep(BaseModel, validate_assignment=True): num_reads: Literal[1] = 1 num_ramps: Literal[1] = 1 + @computed_field @property - def num_groups(self): + def num_groups(self) -> int: """ The number of groups. From 0a821431265e5124abdf184e18db5feaa57d31f1 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 12:35:14 +0200 Subject: [PATCH 101/171] Update the XML schema --- src/aeonlib/salt/models/proposal.xsd | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/proposal.xsd b/src/aeonlib/salt/models/proposal.xsd index 69f06f5..81de584 100644 --- a/src/aeonlib/salt/models/proposal.xsd +++ b/src/aeonlib/salt/models/proposal.xsd @@ -4015,7 +4015,21 @@ + + + + + + + + + + + + + + @@ -4986,7 +5000,7 @@ - + From 9ad0ef73f79d302b36ef7d8f044a0feb512347e4 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 12:44:30 +0200 Subject: [PATCH 102/171] Add template for NIRWALS dither pattern steps --- .../templates/nirwals_dither_pattern_step.xml | 20 +++++++++++++++++++ tests/salt/models/serialize/test_templates.py | 11 ++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/aeonlib/salt/models/serialize/templates/nirwals_dither_pattern_step.xml diff --git a/src/aeonlib/salt/models/serialize/templates/nirwals_dither_pattern_step.xml b/src/aeonlib/salt/models/serialize/templates/nirwals_dither_pattern_step.xml new file mode 100644 index 0000000..967f3cc --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/nirwals_dither_pattern_step.xml @@ -0,0 +1,20 @@ + + + {{ step.horizontal_offset }} + {{ step.vertical_offset }} + + {{ step.offset_type }} + + + {{ step.exposure_time }} + seconds + + 1 + {{ step.gain }} + {{ step.sampling }} + {{ step.num_reads }} + {{ step.num_ramps }} + {{ step.num_groups }} + + {{ step.exposure_type }} + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 6a4c4f1..30346e0 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -261,3 +261,14 @@ def test_rss_wave_plate_pattern_step_values( assert "0_0" in xml assert "4_45.00" in xml assert "28_315.00" in xml + + +class TestNirwalsTemplates: + def test_nirwals_dither_pattern_step(self, base_nirwals_dither_pattern_step): + """Test that the NIRWALS dither pattern step templates generates valid XML.""" + step = base_nirwals_dither_pattern_step + + xml = render_template("nirwals_dither_pattern_step.xml", step=step.model_dump()) + + validate_xml(xml) + assert True From 834c343c537792dd91906edbfd958683debee186 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 13:21:26 +0200 Subject: [PATCH 103/171] Add a Jinja filter for NIRWALS articulation stations --- src/aeonlib/salt/models/util.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index 4a644d4..569ec82 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -108,6 +108,13 @@ def _wave_plate_station(angle): return f"{(angle / 11.25):.0f}_{angle:.2f}" +def _nirwals_articulation_station(angle): + if angle < 1e-5: + return "0_0" + else: + return f"{(2 * angle):.0f}_{angle:.1f}" + + def render_template( template_path: str, loader: BaseLoader | None = None, **kwargs ) -> str: @@ -142,6 +149,7 @@ def render_template( env.trim_blocks = True env.lstrip_blocks = True env.filters["wave_plate_station"] = _wave_plate_station + env.filters["nirwals_articulation_station"] = _nirwals_articulation_station template = env.get_template(template_path) return template.render(**kwargs) From 0b79c338f31422b08f41a1b6081e6ccf7681854a Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 13:27:35 +0200 Subject: [PATCH 104/171] Add a template for NIRWALS setups --- src/aeonlib/salt/models/nirwals_models.py | 6 +-- .../models/serialize/templates/nirwals.xml | 41 ++++++++++++++++ tests/salt/models/serialize/test_templates.py | 48 +++++++++++++++++++ 3 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 src/aeonlib/salt/models/serialize/templates/nirwals.xml diff --git a/src/aeonlib/salt/models/nirwals_models.py b/src/aeonlib/salt/models/nirwals_models.py index d4dd7ae..8cac793 100644 --- a/src/aeonlib/salt/models/nirwals_models.py +++ b/src/aeonlib/salt/models/nirwals_models.py @@ -66,12 +66,10 @@ class Nirwals(BaseModel, validate_assignment=True): """ num_cycles: PositiveInt = 1 - grating: Annotated[NirwalsGrating, LowerCaseValidator, UpperCaseSerializer] + grating: Annotated[NirwalsGrating, LowerCaseValidator] grating_angle: Annotated[Angle, GreaterEqual(0 * u.deg), LessEqual(100 * u.deg)] articulation_angle: Angle - filter: Annotated[NirwalsFilter, LowerCaseValidator, CapitalizingSerializer] = ( - "empty" - ) + filter: Annotated[NirwalsFilter, LowerCaseValidator, UpperCaseSerializer] = "empty" camera_filter: Annotated[ NirwalsCameraFilter, LowerCaseValidator, CapitalizingSerializer ] diff --git a/src/aeonlib/salt/models/serialize/templates/nirwals.xml b/src/aeonlib/salt/models/serialize/templates/nirwals.xml new file mode 100644 index 0000000..ee51af5 --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/nirwals.xml @@ -0,0 +1,41 @@ + + NIRWALS + {{ nirwals.num_cycles }} + + Normal + + {% for step in nirwals.dither_pattern %} + {% include "nirwals_dither_pattern_step.xml" %} + {% endfor %} + + + + {{ nirwals.filter }} + {{ nirwals.camera_filter }} + {{ nirwals.grating }} + + {{ nirwals.grating_angle }} + degrees + + {{ nirwals.articulation_angle|nirwals_articulation_station }} + 97.0 + + {% if nirwals.include_flat %} + + + + {% endif %} + {% if nirwals.include_arc %} + + + + {% endif %} + {% if nirwals.request_spectrophotometric_standard %} + + + Spectrophotometric + + + {% endif %} + false + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 30346e0..78e29e7 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -1,6 +1,7 @@ import pytest import astropy.units as u +from astropy.units import Quantity from aeonlib.salt.models import SalticamFilterSequenceStep from aeonlib.salt.models.util import render_template, validate_xml @@ -272,3 +273,50 @@ def test_nirwals_dither_pattern_step(self, base_nirwals_dither_pattern_step): validate_xml(xml) assert True + + @pytest.mark.parametrize("full", [False, True]) + def test_nirwals(self, full: bool, base_nirwals): + nirwals = base_nirwals + + if full: + nirwals.include_flat = True + nirwals.include_arc = True + nirwals.request_spectrophotometric_standard = True + else: + nirwals.include_flat = False + nirwals.include_arc = False + nirwals.request_spectrophotometric_standard = False + + xml = render_template("nirwals.xml", nirwals=nirwals.model_dump()) + + if full: + assert "NirDefaultCalibrationFlat" in xml + assert "NirDefaultArc" in xml + assert "NirStandard" in xml + else: + assert "NirDefaultCalibrationFlat" not in xml + assert "NirDefaultArc" not in xml + assert "NirStandard" not in xml + + validate_xml(xml) + assert True + + @pytest.mark.parametrize( + "angle, station", + [ + (0 * u.deg, "0_0"), + (0.5 * u.deg, "1_0.5"), + (14 * u.deg, "28_14.0"), + (37.5 * u.deg, "75_37.5"), + (100 * u.deg, "200_100.0"), + ], + ) + def test_nirwals_articulation_station( + self, angle: Quantity, station: str, base_nirwals + ): + nirwals = base_nirwals + nirwals.articulation_angle = angle + + xml = render_template("nirwals.xml", nirwals=nirwals.model_dump()) + + assert f"{station}" in xml From f817a7eb356b62a1c2565b732c20b5700bf8be85 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 13:48:00 +0200 Subject: [PATCH 105/171] Allow any float values for magnitudes --- src/aeonlib/salt/models/target_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py index ca3cb30..e9376d9 100644 --- a/src/aeonlib/salt/models/target_models.py +++ b/src/aeonlib/salt/models/target_models.py @@ -56,9 +56,9 @@ class MagnitudeRange(BaseModel, validate_assignment=True): Bandpass filter for which the magnitude range is given. """ - min_magnitude: NonNegativeFloat + min_magnitude: float - max_magnitude: NonNegativeFloat + max_magnitude: float bandpass: MagnitudeBandpass From 2aab57ec4fed842ae06fbc30f109034d16448b27 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 16:13:09 +0200 Subject: [PATCH 106/171] Add a Jinja filter for converting years to ISO timestamps --- src/aeonlib/salt/models/util.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index 569ec82..fcc248e 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -1,5 +1,7 @@ +import datetime import io import pathlib +import zoneinfo from typing import Any import astropy.units as u @@ -115,6 +117,11 @@ def _nirwals_articulation_station(angle): return f"{(2 * angle):.0f}_{angle:.1f}" +def _year_as_iso_timestamp(year): + t = datetime.datetime(year, 1, 1, 0, 0, 0, 0, tzinfo=zoneinfo.ZoneInfo("UTC")) + return t.isoformat() + + def render_template( template_path: str, loader: BaseLoader | None = None, **kwargs ) -> str: @@ -150,6 +157,7 @@ def render_template( env.lstrip_blocks = True env.filters["wave_plate_station"] = _wave_plate_station env.filters["nirwals_articulation_station"] = _nirwals_articulation_station + env.filters["year_as_iso_timestamp"] = _year_as_iso_timestamp template = env.get_template(template_path) return template.render(**kwargs) From bf5d7bbb01efc3b466340618060ea89334022cac Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 16:13:57 +0200 Subject: [PATCH 107/171] Add validation to SALT targets --- src/aeonlib/salt/models/target_models.py | 22 +++++++++++++++++++- tests/salt/models/test_target_models.py | 26 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py index e9376d9..5a287a5 100644 --- a/src/aeonlib/salt/models/target_models.py +++ b/src/aeonlib/salt/models/target_models.py @@ -12,7 +12,7 @@ from aeonlib.salt.validators import check_in_visibility_range -class SaltSiderealTarget(SiderealTarget): +class SaltSiderealTarget(SiderealTarget, validate_assignment=True): """ A sidereal target to observe with SALT. @@ -32,6 +32,26 @@ class SaltSiderealTarget(SiderealTarget): target_type: TargetType magnitude_range: MagnitudeRange + @field_validator("type", mode="after") + @classmethod + def check_type(cls, value: str): + if value != "ICRS": + raise ValueError("SALT only supports targets of type ICRS.") + return value + + @model_validator(mode="after") + def check_coordinates(self): + if self.hour_angle is not None: + raise ValueError("SALT does not support hour angle values.") + + if self.altitude is not None: + raise ValueError("SALT does not support altitude values.") + + if self.azimuth is not None: + raise ValueError("SALT does not support azimuth values.") + + return self + @field_validator("dec", mode="after") @classmethod def check_declination_viewable(cls, value: astropy.coordinates.Angle): diff --git a/tests/salt/models/test_target_models.py b/tests/salt/models/test_target_models.py index 9049258..55cd3cc 100644 --- a/tests/salt/models/test_target_models.py +++ b/tests/salt/models/test_target_models.py @@ -1,6 +1,7 @@ from contextlib import nullcontext import astropy.coordinates +import astropy.units as u import pytest from pydantic import ValidationError @@ -12,6 +13,31 @@ def test_salt_sidereal_target(self, base_target): """Test that a simple target can be built.""" assert True + @pytest.mark.parametrize("target_type", ["ALTAZ", "HOUR_ANGLE"]) + def test_type(self, target_type: str, base_target): + """Test that the target type must be ICRS.""" + target = base_target + with pytest.raises(ValueError, match="SALT"): + target.type = target_type + + def test_hour_angle_must_not_exist(self, base_target): + """Test that no hour angle must be defined.""" + target = base_target + with pytest.raises(ValueError, match="hour angle"): + target.hour_angle = 45 * u.deg + + def test_altitude_must_not_exist(self, base_target): + """Test that no altitude must be defined.""" + target = base_target + with pytest.raises(ValueError, match="altitude"): + target.altitude = 45 * u.deg + + def test_azimuth_must_not_exist(self, base_target): + """Test that no azimuth must be defined.""" + target = base_target + with pytest.raises(ValueError, match="azimuth"): + target.azimuth = 45 * u.deg + @pytest.mark.parametrize( "dec, expectation", [ From f15bed2d5d60a2e50dea8102ea201fc2e0b62a2a Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 16:14:16 +0200 Subject: [PATCH 108/171] Add template for SALT targets --- .../models/serialize/templates/target.xml | 38 +++++++++++++ tests/salt/models/serialize/test_templates.py | 53 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/aeonlib/salt/models/serialize/templates/target.xml diff --git a/src/aeonlib/salt/models/serialize/templates/target.xml b/src/aeonlib/salt/models/serialize/templates/target.xml new file mode 100644 index 0000000..0cef31c --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/target.xml @@ -0,0 +1,38 @@ + + {{ target.name }} + b0081d59-bd08-46f8-aa11-0c2be0544a41 + SuperSoft + + + 0 + 0 + 0.0 + + + - + 0 + 0 + 0.0 + + {% if target.proper_motion_ra or target.proper_motion_dec %} + + + {{ target.proper_motion_ra }} + arcseconds/year + + + {{ target.proper_motion_dec }} + arcseconds/year + + {{ target.epoch | year_as_iso_timestamp }} + + {% endif %} + 2000.0 + + + {{ target.magnitude_range.bandpass }} + {{ target.magnitude_range.min_magnitude }} + {{ target.magnitude_range.max_magnitude }} + + true + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 78e29e7..0a48b27 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -320,3 +320,56 @@ def test_nirwals_articulation_station( xml = render_template("nirwals.xml", nirwals=nirwals.model_dump()) assert f"{station}" in xml + + +class TestTarget: + @pytest.mark.parametrize("full", [False, True]) + def test_target(self, full: bool, base_target): + target = base_target + + if full: + target.proper_motion_ra = 17 + target.proper_motion_dec = -3 + else: + target.proper_motion_ra = 0 + target.proper_motion_dec = 0 + + xml = render_template("target.xml", target=target.model_dump()) + + if full: + assert "RightAscensionDot" in xml + assert "DeclinationDot" in xml + else: + assert "RightAscensionDot" not in xml + assert "DeclinationDot" not in xml + + validate_xml(xml) + assert True + + @pytest.mark.parametrize( + "proper_motion_ra, proper_motion_dec, required", + [(0, 0, False), (0, 34, True), (45, 0, True), (-5, -7, True)], + ) + def test_proper_motion( + self, + proper_motion_ra: float, + proper_motion_dec: float, + required: bool, + base_target, + ): + target = base_target + + target.proper_motion_ra = proper_motion_ra + target.proper_motion_dec = proper_motion_dec + + xml = render_template("target.xml", target=target.model_dump()) + + if required: + assert "RightAscensionDot" in xml + assert "DeclinationDot" in xml + else: + assert "RightAscensionDot" not in xml + assert "DeclinationDot" not in xml + + validate_xml(xml) + assert True From 594407270419f71eae9c47544adcd7a237c60d78 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 16:15:32 +0200 Subject: [PATCH 109/171] Remove an unnecessary import --- src/aeonlib/salt/models/target_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py index 5a287a5..80f39ee 100644 --- a/src/aeonlib/salt/models/target_models.py +++ b/src/aeonlib/salt/models/target_models.py @@ -5,7 +5,7 @@ from typing import Self import astropy.coordinates -from pydantic import BaseModel, NonNegativeFloat, field_validator, model_validator +from pydantic import BaseModel, field_validator, model_validator from aeonlib.models import SiderealTarget from aeonlib.salt.models.types import MagnitudeBandpass, TargetType From 3fae21f0b41fc8a44873a5ea8b04a8a454898c60 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 16:46:58 +0200 Subject: [PATCH 110/171] Add a global function for generating UUID strings in a Jinja template --- src/aeonlib/salt/models/util.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index fcc248e..e7ead76 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -1,6 +1,7 @@ import datetime import io import pathlib +import uuid import zoneinfo from typing import Any @@ -158,6 +159,7 @@ def render_template( env.filters["wave_plate_station"] = _wave_plate_station env.filters["nirwals_articulation_station"] = _nirwals_articulation_station env.filters["year_as_iso_timestamp"] = _year_as_iso_timestamp + env.globals.update({"uuid": _uuid}) template = env.get_template(template_path) return template.render(**kwargs) @@ -168,6 +170,10 @@ def _lower(s: Any) -> Any: return s +def _uuid() -> str: + return str(uuid.uuid4()) + + LowerCaseValidator = BeforeValidator(_lower) From c5dc9d165884fb647d28c6255a155775ab81e57f Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 16:47:30 +0200 Subject: [PATCH 111/171] Generate UUID strings for the target id and target code --- src/aeonlib/salt/models/serialize/templates/target.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeonlib/salt/models/serialize/templates/target.xml b/src/aeonlib/salt/models/serialize/templates/target.xml index 0cef31c..4e1b5be 100644 --- a/src/aeonlib/salt/models/serialize/templates/target.xml +++ b/src/aeonlib/salt/models/serialize/templates/target.xml @@ -1,6 +1,6 @@ - + {{ target.name }} - b0081d59-bd08-46f8-aa11-0c2be0544a41 + {{ uuid() }} SuperSoft From f7ffc96ea5d99e3d7aa79ba9abd40a4882f7b799 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 16:47:30 +0200 Subject: [PATCH 112/171] Generate UUID strings for the target id and target code --- src/aeonlib/salt/models/serialize/templates/target.xml | 4 ++-- src/aeonlib/salt/models/util.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/aeonlib/salt/models/serialize/templates/target.xml b/src/aeonlib/salt/models/serialize/templates/target.xml index 0cef31c..4e1b5be 100644 --- a/src/aeonlib/salt/models/serialize/templates/target.xml +++ b/src/aeonlib/salt/models/serialize/templates/target.xml @@ -1,6 +1,6 @@ - + {{ target.name }} - b0081d59-bd08-46f8-aa11-0c2be0544a41 + {{ uuid() }} SuperSoft diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index e7ead76..84525de 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -123,6 +123,10 @@ def _year_as_iso_timestamp(year): return t.isoformat() +def _uuid() -> str: + return str(uuid.uuid4()) + + def render_template( template_path: str, loader: BaseLoader | None = None, **kwargs ) -> str: From ee5eaac07cdc1aded1af378964083490ad93aa29 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 18:19:39 +0200 Subject: [PATCH 113/171] Add a global function to Jinja for converting a float into an Angle onject --- src/aeonlib/salt/models/util.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index 84525de..9dd767b 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -127,6 +127,10 @@ def _uuid() -> str: return str(uuid.uuid4()) +def _to_angle(degrees: float) -> Angle: + return Angle(degrees * u.deg) + + def render_template( template_path: str, loader: BaseLoader | None = None, **kwargs ) -> str: @@ -163,7 +167,7 @@ def render_template( env.filters["wave_plate_station"] = _wave_plate_station env.filters["nirwals_articulation_station"] = _nirwals_articulation_station env.filters["year_as_iso_timestamp"] = _year_as_iso_timestamp - env.globals.update({"uuid": _uuid}) + env.globals.update({"uuid": _uuid, "to_angle": _to_angle}) template = env.get_template(template_path) return template.render(**kwargs) @@ -174,10 +178,6 @@ def _lower(s: Any) -> Any: return s -def _uuid() -> str: - return str(uuid.uuid4()) - - LowerCaseValidator = BeforeValidator(_lower) From 544845bdc6d7355d188b077c341d26c850cf833d Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 18:28:44 +0200 Subject: [PATCH 114/171] Add a Jinja filter for getting the sign of a number --- src/aeonlib/salt/models/util.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index 9dd767b..bbc1eeb 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -123,6 +123,10 @@ def _year_as_iso_timestamp(year): return t.isoformat() +def _sign(value): + return "+" if value >= 0 else "-" + + def _uuid() -> str: return str(uuid.uuid4()) @@ -167,6 +171,7 @@ def render_template( env.filters["wave_plate_station"] = _wave_plate_station env.filters["nirwals_articulation_station"] = _nirwals_articulation_station env.filters["year_as_iso_timestamp"] = _year_as_iso_timestamp + env.filters["sign"] = _sign env.globals.update({"uuid": _uuid, "to_angle": _to_angle}) template = env.get_template(template_path) return template.render(**kwargs) From 2e6997115221677bb4b7f65f78feccce3bacd55f Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 18:29:03 +0200 Subject: [PATCH 115/171] Add the missing values to the target template --- .../models/serialize/templates/target.xml | 16 +++--- tests/salt/models/serialize/test_templates.py | 49 +++++++++++++++++++ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/aeonlib/salt/models/serialize/templates/target.xml b/src/aeonlib/salt/models/serialize/templates/target.xml index 4e1b5be..b35ea1e 100644 --- a/src/aeonlib/salt/models/serialize/templates/target.xml +++ b/src/aeonlib/salt/models/serialize/templates/target.xml @@ -1,18 +1,18 @@ {{ target.name }} {{ uuid() }} - SuperSoft + {{ target.target_type }} - 0 - 0 - 0.0 + {{ to_angle(target.ra).hms[0] | round(0) | int }} + {{ to_angle(target.ra).hms[1] | round(0) | int }} + {{ to_angle(target.ra).hms[2] | round(5) }} - - - 0 - 0 - 0.0 + {{ target.dec | sign }} + {{ to_angle(target.dec).dms[0] | abs | round(0) | int }} + {{ to_angle(target.dec).dms[1] | abs | round(0) | int }} + {{ to_angle(target.dec).dms[2] | abs | round(5) }} {% if target.proper_motion_ra or target.proper_motion_dec %} diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 0a48b27..c463863 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -1,6 +1,7 @@ import pytest import astropy.units as u +from astropy.coordinates import Angle from astropy.units import Quantity from aeonlib.salt.models import SalticamFilterSequenceStep @@ -346,6 +347,54 @@ def test_target(self, full: bool, base_target): validate_xml(xml) assert True + @pytest.mark.parametrize( + "ra, hours, minutes, seconds", + [ + ("0d", "0", "0", "0.0"), + ("13h 24m 34.67s", "13", "24", "34.67"), + ("22.5d", "1", "30", "0.0"), + ], + ) + def test_right_ascension( + self, ra: str, hours: str, minutes: str, seconds: str, base_target + ): + target = base_target + target.ra = Angle(ra) + + xml = render_template("target.xml", target=target.model_dump()) + + assert f"{hours}" in xml + assert f"{minutes}" in xml + assert f"{seconds}" in xml + + @pytest.mark.parametrize( + "dec, sign, degrees, arcminutes, arcseconds", + [ + ("0d", "+", "0", "0", "0.0"), + ("-53d 13m 47.44s", "-", "53", "13", "47.44"), + ("+6d 30m 17.6s", "+", "6", "30", "17.6"), + ("10d", "+", "10", "0", "0.0"), + ], + ) + def test_declination( + self, + sign: str, + dec: str, + degrees: str, + arcminutes: str, + arcseconds: str, + base_target, + ): + target = base_target + target.dec = dec + + xml = render_template("target.xml", target=target.model_dump()) + + assert f"{sign}" in xml + assert f"{degrees}" in xml + assert f"{arcminutes}" in xml + assert f"{arcseconds}" in xml + @pytest.mark.parametrize( "proper_motion_ra, proper_motion_dec, required", [(0, 0, False), (0, 34, True), (45, 0, True), (-5, -7, True)], From 6fb6ef09cc76c7f93973af2c04c3ff1e92f9f952 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 20:09:48 +0200 Subject: [PATCH 116/171] Refactor the code --- src/aeonlib/salt/models/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index bbc1eeb..2995060 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -172,7 +172,8 @@ def render_template( env.filters["nirwals_articulation_station"] = _nirwals_articulation_station env.filters["year_as_iso_timestamp"] = _year_as_iso_timestamp env.filters["sign"] = _sign - env.globals.update({"uuid": _uuid, "to_angle": _to_angle}) + env.globals["uuid"] = _uuid + env.globals["to_angle"] = _to_angle template = env.get_template(template_path) return template.render(**kwargs) From 9e4d15dab7af61915e1df91d131002a6b2d7e138 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 20:24:57 +0200 Subject: [PATCH 117/171] Add docstrings --- tests/salt/models/serialize/test_templates.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index c463863..c4699db 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -277,6 +277,7 @@ def test_nirwals_dither_pattern_step(self, base_nirwals_dither_pattern_step): @pytest.mark.parametrize("full", [False, True]) def test_nirwals(self, full: bool, base_nirwals): + """Test that the NIRWALS template generates valid XML.""" nirwals = base_nirwals if full: @@ -315,6 +316,7 @@ def test_nirwals(self, full: bool, base_nirwals): def test_nirwals_articulation_station( self, angle: Quantity, station: str, base_nirwals ): + """Test that the NIRWALS articulation station is output correctly.""" nirwals = base_nirwals nirwals.articulation_angle = angle @@ -326,6 +328,7 @@ def test_nirwals_articulation_station( class TestTarget: @pytest.mark.parametrize("full", [False, True]) def test_target(self, full: bool, base_target): + """Test that the target template generates valid XML.""" target = base_target if full: @@ -358,6 +361,7 @@ def test_target(self, full: bool, base_target): def test_right_ascension( self, ra: str, hours: str, minutes: str, seconds: str, base_target ): + """Test that the right ascension is mapped correctly to XML.""" target = base_target target.ra = Angle(ra) @@ -385,6 +389,7 @@ def test_declination( arcseconds: str, base_target, ): + """Test that the declination is mapped correctly to XML.""" target = base_target target.dec = dec @@ -406,6 +411,7 @@ def test_proper_motion( required: bool, base_target, ): + """Test that the proper motion is mapped correctly to XML.""" target = base_target target.proper_motion_ra = proper_motion_ra From c1ab100059a3a10019b754b82ef7707c129bc951 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 20:38:44 +0200 Subject: [PATCH 118/171] Use a computed field for the HRS precision radial velocity calibration --- src/aeonlib/salt/models/hrs_models.py | 42 ++++++------------------ src/aeonlib/salt/models/types/hrs.py | 2 +- tests/salt/models/test_hrs_models.py | 46 +++------------------------ 3 files changed, 16 insertions(+), 74 deletions(-) diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py index 98c05d8..8cb47cc 100644 --- a/src/aeonlib/salt/models/hrs_models.py +++ b/src/aeonlib/salt/models/hrs_models.py @@ -1,9 +1,13 @@ from __future__ import annotations -from typing import Annotated, Self +from typing import Annotated from astropy import units as u -from pydantic import BaseModel, Field, PositiveInt, model_validator, PlainSerializer +from pydantic import ( + BaseModel, + PositiveInt, + computed_field, +) from aeonlib.salt.models.util import LowerCaseValidator, UpperCaseSerializer from aeonlib.salt.models.types import HrsMode, HrsPrvCalibration, PositiveDuration @@ -42,42 +46,16 @@ class Hrs(BaseModel, validate_assignment=True): num_cycles: PositiveInt = 1 mode: Annotated[HrsMode, LowerCaseValidator, UpperCaseSerializer] - prv_calibration: Annotated[ - HrsPrvCalibration | None, - LowerCaseValidator, - PlainSerializer(Hrs.serialize_prv_calibration), - ] = Field( - default_factory=lambda data: ( - "thar" if data["mode"] == "high stability" else None - ) - ) fibre_separation: Annotated[ Angle, GreaterEqual(16 * u.arcsec), LessEqual(63 * u.arcsec) ] = 60 * u.arcsec blue_arm: HrsDetector red_arm: HrsDetector - @model_validator(mode="after") - def check_prv_calibration(self) -> Self: - if self.mode == "high stability": - if self.prv_calibration != "thar": - raise ValueError( - 'prv_calibration must be "ThAr" (or "thar") for the high stability mode.' - ) - else: - if self.prv_calibration is not None: - raise ValueError( - f"prv_calibration must be None for the {self.mode} mode." - ) - return self - - @staticmethod - def serialize_prv_calibration(value: str | None) -> str | None: - if value is None: - return None - if value == "thar": - return "ThAr" - raise ValueError(f"Precision radial velocity cannot be serialized: {value}") + @computed_field + @property + def prv_calibration(self) -> HrsPrvCalibration | None: + return "ThAr" if self.mode == "high stability" else None class HrsDetector(BaseModel, validate_assignment=True): diff --git a/src/aeonlib/salt/models/types/hrs.py b/src/aeonlib/salt/models/types/hrs.py index 0ddac08..8bb9634 100644 --- a/src/aeonlib/salt/models/types/hrs.py +++ b/src/aeonlib/salt/models/types/hrs.py @@ -6,5 +6,5 @@ """An HRS instrument mode.""" -HrsPrvCalibration = Literal["ThAr", "thar"] +HrsPrvCalibration = Literal["ThAr"] """An HRS precision radial velocity calibration.""" diff --git a/tests/salt/models/test_hrs_models.py b/tests/salt/models/test_hrs_models.py index 3296ea2..a51434f 100644 --- a/tests/salt/models/test_hrs_models.py +++ b/tests/salt/models/test_hrs_models.py @@ -1,8 +1,5 @@ -from contextlib import nullcontext - import pytest -from aeonlib.salt.models import Hrs from aeonlib.salt.models.types import HrsMode @@ -17,53 +14,20 @@ def test_hrs(self, base_hrs): ("low resolution", None), ("medium resolution", None), ("high resolution", None), - ("high stability", "thar"), + ("high stability", "ThAr"), ], ) - def test_default_prv_calibration(self, mode: HrsMode, prv_calibration, base_hrs): + def test_prv_calibration(self, mode: HrsMode, prv_calibration, base_hrs): # Test that the default value for the precision radial velocity calibration is # correct. - hrs = Hrs( - num_cycles=base_hrs.num_cycles, - mode=mode, - fibre_separation=base_hrs.fibre_separation, - blue_arm=base_hrs.blue_arm, - red_arm=base_hrs.red_arm, - ) + hrs = base_hrs + hrs.mode = mode + if prv_calibration is not None: assert hrs.prv_calibration == prv_calibration else: assert hrs.prv_calibration is None - @pytest.mark.parametrize( - "mode, prv_calibration_is_none", - [ - ("low resolution", True), - ("medium resolution", True), - ("high resolution", True), - ("high stability", False), - ], - ) - def test_allowed_prv_calibration_depends_on_mode( - self, mode: HrsMode, prv_calibration_is_none, base_hrs - ): - # Test that the precision radial velocity calibration must be "thar" for the - # high stability mode and None for all other modes. - hrs_data = base_hrs.model_dump() - del hrs_data["mode"] - if "prv_calibration" in hrs_data: - del hrs_data["prv_calibration"] - if prv_calibration_is_none: - with pytest.raises(ValueError): - Hrs(**hrs_data, mode=mode, prv_calibration="ThAr") - with nullcontext(): - Hrs(**hrs_data, mode=mode, prv_calibration=None) - else: - with nullcontext(): - Hrs(**hrs_data, mode=mode, prv_calibration="ThAr") - with pytest.raises(ValueError): - Hrs(**hrs_data, mode=mode, prv_calibration=None) - class TestHrsDetector: def test_hrs_detector(self, base_hrs_detector): From d8280bfe4d55fe06c64f7e7659e57e221f7fe1cb Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 21:05:02 +0200 Subject: [PATCH 119/171] Add a Jinja filter for displaying the iodine cell position --- src/aeonlib/salt/models/util.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index 2995060..cc13b3b 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -111,6 +111,10 @@ def _wave_plate_station(angle): return f"{(angle / 11.25):.0f}_{angle:.2f}" +def _iodine_cell_position(value): + return value if value else "OUT" + + def _nirwals_articulation_station(angle): if angle < 1e-5: return "0_0" @@ -169,6 +173,7 @@ def render_template( env.trim_blocks = True env.lstrip_blocks = True env.filters["wave_plate_station"] = _wave_plate_station + env.filters["iodine_cell_position"] = _iodine_cell_position env.filters["nirwals_articulation_station"] = _nirwals_articulation_station env.filters["year_as_iso_timestamp"] = _year_as_iso_timestamp env.filters["sign"] = _sign From 20e3fe697c2cfd982a1d0ec3bce0cda05828252a Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 10 Feb 2026 21:05:18 +0200 Subject: [PATCH 120/171] Add a template for HRS setups --- .../salt/models/serialize/templates/hrs.xml | 53 +++++++++++++++++++ tests/salt/models/serialize/test_templates.py | 25 ++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 src/aeonlib/salt/models/serialize/templates/hrs.xml diff --git a/src/aeonlib/salt/models/serialize/templates/hrs.xml b/src/aeonlib/salt/models/serialize/templates/hrs.xml new file mode 100644 index 0000000..e297c15 --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/hrs.xml @@ -0,0 +1,53 @@ + + HRS + + {{ hrs.mode }} + Science + {% with v=hrs.prv_calibration %} + {{ v | iodine_cell_position }} + {% endwith %} + Star + + {{ to_angle(hrs.fibre_separation).arcsec }} + seconds + + true + + + {{ hrs.num_cycles }} + + {% for exposure_time in hrs.blue_arm.exposure_times %} + + {{ exposure_time }} + seconds + + {% endfor %} + + + {% for exposure_time in hrs.red_arm.exposure_times %} + + {{ exposure_time }} + seconds + + {% endfor %} + + + + 1 + Slow + 1 + 1 + 1 + 0 + 0 + + + 1 + Slow + 1 + 1 + 1 + 0 + 0 + + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index c4699db..0276ff3 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -1,6 +1,5 @@ -import pytest - import astropy.units as u +import pytest from astropy.coordinates import Angle from astropy.units import Quantity @@ -428,3 +427,25 @@ def test_proper_motion( validate_xml(xml) assert True + + +class TestHrs: + @pytest.mark.parametrize( + "mode, iodine_cell_position", + [ + ("low resolution", "OUT"), + ("medium resolution", "OUT"), + ("high resolution", "OUT"), + ("high stability", "ThAr"), + ], + ) + def test_hrs(self, mode: str, iodine_cell_position: str, base_hrs): + hrs = base_hrs + hrs.mode = mode + + xml = render_template("hrs.xml", hrs=hrs.model_dump()) + + assert f"{iodine_cell_position}" in xml + + validate_xml(xml) + assert True From 79ff07e39390f39332c7de650ef645b5f0ca4597 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 11 Feb 2026 14:16:10 +0200 Subject: [PATCH 121/171] Update the XML schema --- src/aeonlib/salt/models/proposal.xsd | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/aeonlib/salt/models/proposal.xsd b/src/aeonlib/salt/models/proposal.xsd index 81de584..30cfad4 100644 --- a/src/aeonlib/salt/models/proposal.xsd +++ b/src/aeonlib/salt/models/proposal.xsd @@ -637,6 +637,13 @@ + + + + + + + @@ -3849,7 +3856,7 @@ - + @@ -3875,7 +3882,7 @@ - + @@ -5000,7 +5007,7 @@ - + From 4268cbbc3be3e3f38fa66c4b964ef66d6b6dd244 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 11 Feb 2026 14:16:46 +0200 Subject: [PATCH 122/171] Update the NIRWALS templates to conform to the latest XML schema --- src/aeonlib/salt/models/serialize/templates/nirwals.xml | 4 ++-- .../serialize/templates/nirwals_dither_pattern_step.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/aeonlib/salt/models/serialize/templates/nirwals.xml b/src/aeonlib/salt/models/serialize/templates/nirwals.xml index ee51af5..08af031 100644 --- a/src/aeonlib/salt/models/serialize/templates/nirwals.xml +++ b/src/aeonlib/salt/models/serialize/templates/nirwals.xml @@ -3,11 +3,11 @@ {{ nirwals.num_cycles }} Normal - + {% for step in nirwals.dither_pattern %} {% include "nirwals_dither_pattern_step.xml" %} {% endfor %} - + {{ nirwals.filter }} diff --git a/src/aeonlib/salt/models/serialize/templates/nirwals_dither_pattern_step.xml b/src/aeonlib/salt/models/serialize/templates/nirwals_dither_pattern_step.xml index 967f3cc..9183ad5 100644 --- a/src/aeonlib/salt/models/serialize/templates/nirwals_dither_pattern_step.xml +++ b/src/aeonlib/salt/models/serialize/templates/nirwals_dither_pattern_step.xml @@ -1,4 +1,4 @@ - + {{ step.horizontal_offset }} {{ step.vertical_offset }} @@ -17,4 +17,4 @@ {{ step.num_groups }} {{ step.exposure_type }} - + From 34478efffc3286ff6b62faff6f5cf0f0dbb1ed47 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 11 Feb 2026 15:54:28 +0200 Subject: [PATCH 123/171] Fix the proper motion values in the XML --- src/aeonlib/salt/models/serialize/templates/target.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeonlib/salt/models/serialize/templates/target.xml b/src/aeonlib/salt/models/serialize/templates/target.xml index b35ea1e..9bdb26b 100644 --- a/src/aeonlib/salt/models/serialize/templates/target.xml +++ b/src/aeonlib/salt/models/serialize/templates/target.xml @@ -17,11 +17,11 @@ {% if target.proper_motion_ra or target.proper_motion_dec %} - {{ target.proper_motion_ra }} + {{ target.proper_motion_ra / 1000 }} arcseconds/year - {{ target.proper_motion_dec }} + {{ target.proper_motion_dec / 1000 }} arcseconds/year {{ target.epoch | year_as_iso_timestamp }} From af3734c89ca9335492456282495a63022fd4d281 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 11 Feb 2026 17:17:10 +0200 Subject: [PATCH 124/171] Update the XML schema --- src/aeonlib/salt/models/proposal.xsd | 4 ++-- src/aeonlib/salt/models/serialize/templates/hrs.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aeonlib/salt/models/proposal.xsd b/src/aeonlib/salt/models/proposal.xsd index 30cfad4..911e5b0 100644 --- a/src/aeonlib/salt/models/proposal.xsd +++ b/src/aeonlib/salt/models/proposal.xsd @@ -637,7 +637,7 @@ - + @@ -3295,7 +3295,7 @@ - + diff --git a/src/aeonlib/salt/models/serialize/templates/hrs.xml b/src/aeonlib/salt/models/serialize/templates/hrs.xml index e297c15..3515bd2 100644 --- a/src/aeonlib/salt/models/serialize/templates/hrs.xml +++ b/src/aeonlib/salt/models/serialize/templates/hrs.xml @@ -9,7 +9,7 @@ Star {{ to_angle(hrs.fibre_separation).arcsec }} - seconds + arcseconds true From df6995415364c4cd80be43a4561b52169990aebc Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 11 Feb 2026 17:18:28 +0200 Subject: [PATCH 125/171] Update the Acquisition model --- src/aeonlib/salt/models/block_models.py | 30 +++++++++++- tests/salt/models/conftest.py | 2 +- tests/salt/models/test_block_models.py | 61 ++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index df25569..9ff3493 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -14,6 +14,7 @@ PositiveFloat, model_validator, AfterValidator, + Field, ) from aeonlib.models import Angle, Window @@ -197,8 +198,16 @@ class Acquisition(BaseModel, validate_assignment=True): reference_star Reference star on which to acquire. This is only needed if acquiring on the target itself is unfeasible. - include_in_focus_image - Whether an in-focus acquisition image is required. + position_angle + Position angle for the observation. This can be an angle or the string + "parallactic". + do_not_flip_position_angle + Whether the position angle may be flipped by 180 degrees. This must not be None + if the position angle value is angle (rather than "parallactic" or None). If the + position angle value is not an angle, the value of do_not_flip_position_angle is + ignored. + include_focused_image + Whether an in-focus focused acquisition image is required. """ finder_charts: list[FilePath] @@ -206,9 +215,26 @@ class Acquisition(BaseModel, validate_assignment=True): "Johnson V" ) exposure_time: PositiveDuration = 1.0 * u.s + position_angle: Annotated[Angle | Literal["parallactic"] | None, LowerCaseValidator] reference_star: ReferenceStar | None = None + do_not_flip_position_angle: bool | None = Field( + default_factory=lambda data: None + if isinstance(data["position_angle"], str) or data["position_angle"] is None + else False + ) include_focused_image: bool = False + @model_validator(mode="after") + def check_do_not_flip_position_angle(self): + if not isinstance(self.position_angle, str) and self.position_angle is not None: + if self.do_not_flip_position_angle is None: + raise ValueError( + "The do_not_flip_position_angle property must not be None if the " + "position_angle property is an angle." + ) + + return self + class ReferenceStar(BaseModel, validate_assignment=True): """ diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 19c1d7e..97fe8a7 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -87,7 +87,7 @@ def base_acquisition(): finder_chart = ( pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart.pdf" ) - return Acquisition(finder_charts=[finder_chart]) + return Acquisition(finder_charts=[finder_chart], position_angle=45 * u.deg) @pytest.fixture() diff --git a/tests/salt/models/test_block_models.py b/tests/salt/models/test_block_models.py index 99b90c3..70ab0ac 100644 --- a/tests/salt/models/test_block_models.py +++ b/tests/salt/models/test_block_models.py @@ -1,10 +1,11 @@ from contextlib import nullcontext +from typing import Any import astropy.coordinates import pytest from pydantic import ValidationError -from aeonlib.salt.models import Block +from aeonlib.salt.models import Block, Acquisition from aeonlib.salt.models.block_models import ReferenceStar @@ -50,6 +51,64 @@ def test_acquisition(self, base_acquisition): """Test that acquisitions can be built.""" assert True + @pytest.mark.parametrize( + "position_angle, do_not_flip, expected", + [ + (45, True, nullcontext()), + (-34, False, nullcontext()), + (124, None, pytest.raises(ValueError)), + ("parallactic", True, nullcontext()), + ("parallactic", False, nullcontext()), + ("parallactic", None, nullcontext()), + (None, True, nullcontext()), + (None, False, nullcontext()), + (None, None, nullcontext()), + ], + ) + def test_do_not_flip_position_angle( + self, position_angle: Any, do_not_flip: bool | None, expected, base_acquisition + ): + """ + Test that the do_not_flip_position_angle field must be True or False if the + position angle value is an actual angle (rather than "parallactic" or None) and + must be None otherwise. + """ + a = base_acquisition + with expected: + Acquisition( + finder_charts=a.finder_charts, + filter=a.filter, + exposure_time=a.exposure_time, + reference_star=a.reference_star, + position_angle=position_angle, + do_not_flip_position_angle=do_not_flip, + include_focused_image=a.include_focused_image, + ) + + @pytest.mark.parametrize( + "position_angle, do_not_flip", + [(63, False), ("parallactic", None), (None, None)], + ) + def test_default_do_not_flip_position_angle( + self, position_angle: Any, do_not_flip: bool | None, base_acquisition + ): + """ + Test that the default value for the do_not_flip_position_angle field is correct. + """ + a = base_acquisition + acquisition = Acquisition( + finder_charts=a.finder_charts, + filter=a.filter, + exposure_time=a.exposure_time, + reference_star=a.reference_star, + position_angle=position_angle, + include_focused_image=a.include_focused_image, + ) + if do_not_flip is not None: + assert acquisition.do_not_flip_position_angle == do_not_flip + else: + assert acquisition.do_not_flip_position_angle is None + class TestReferenceStar: def test_reference_star(self, base_reference_star): From 33d66df2516c9181a42d13b1ab31950f7334ae00 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 11 Feb 2026 19:55:41 +0200 Subject: [PATCH 126/171] Add the template for acquisitions --- .../serialize/templates/acquisition.xml | 54 ++++++++++++++++ tests/salt/models/serialize/test_templates.py | 63 ++++++++++++++----- 2 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 src/aeonlib/salt/models/serialize/templates/acquisition.xml diff --git a/src/aeonlib/salt/models/serialize/templates/acquisition.xml b/src/aeonlib/salt/models/serialize/templates/acquisition.xml new file mode 100644 index 0000000..83cc94e --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/acquisition.xml @@ -0,0 +1,54 @@ + + Acquisition + {% include "target.xml" %} + + Telescope Configuration + 1 + + Payload Configuration + Acquisition + None + false + true + + Salticam Configuration + 1 + + + {{ acquisition.filter }} + + {{ acquisition.exposure_time }} + seconds + + + + + Normal + 2 + 2 + Science + Bright + Fast + 1 + + 0 + false + + + + {% for finder_chart in finder_charts %} + + {{ finder_chart }} + + {% endfor %} + {% if acquisition.reference_star %} + + {{ acquisition.reference_star.ra }} + {{ acquisition.reference_star.dec }} + {{ acquisition.reference_star.equinox }} + + {% endif %} + {% if acquisition.include_focused_image %} + + {% endif %} + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 0276ff3..d29ec79 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -264,6 +264,28 @@ def test_rss_wave_plate_pattern_step_values( assert "28_315.00" in xml +class TestHrs: + @pytest.mark.parametrize( + "mode, iodine_cell_position", + [ + ("low resolution", "OUT"), + ("medium resolution", "OUT"), + ("high resolution", "OUT"), + ("high stability", "ThAr"), + ], + ) + def test_hrs(self, mode: str, iodine_cell_position: str, base_hrs): + hrs = base_hrs + hrs.mode = mode + + xml = render_template("hrs.xml", hrs=hrs.model_dump()) + + assert f"{iodine_cell_position}" in xml + + validate_xml(xml) + assert True + + class TestNirwalsTemplates: def test_nirwals_dither_pattern_step(self, base_nirwals_dither_pattern_step): """Test that the NIRWALS dither pattern step templates generates valid XML.""" @@ -429,23 +451,34 @@ def test_proper_motion( assert True -class TestHrs: - @pytest.mark.parametrize( - "mode, iodine_cell_position", - [ - ("low resolution", "OUT"), - ("medium resolution", "OUT"), - ("high resolution", "OUT"), - ("high stability", "ThAr"), - ], - ) - def test_hrs(self, mode: str, iodine_cell_position: str, base_hrs): - hrs = base_hrs - hrs.mode = mode +class TestAcquisition: + @pytest.mark.parametrize("full", [False, True]) + def test_acquisition( + self, full: bool, base_acquisition, base_reference_star, base_target + ): + """Test that the acquisition template generates valid XML.""" + acquisition = base_acquisition + target = base_target - xml = render_template("hrs.xml", hrs=hrs.model_dump()) + if full: + acquisition.reference_star = base_reference_star + acquisition.include_focused_image = True + else: + acquisition.reference_star = None + acquisition.include_focused_image = False - assert f"{iodine_cell_position}" in xml + xml = render_template( + "acquisition.xml", + acquisition=acquisition.model_dump(), + target=target.model_dump(), + ) + + if full: + assert "" in xml + assert "" in xml + else: + assert "" not in xml + assert "" not in xml validate_xml(xml) assert True From 655c5f0bd84b7fc168bb34ac78dbe05a6afa759d Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 11 Feb 2026 20:15:20 +0200 Subject: [PATCH 127/171] Update the XML schema --- src/aeonlib/salt/models/proposal.xsd | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/aeonlib/salt/models/proposal.xsd b/src/aeonlib/salt/models/proposal.xsd index 911e5b0..67707c0 100644 --- a/src/aeonlib/salt/models/proposal.xsd +++ b/src/aeonlib/salt/models/proposal.xsd @@ -708,9 +708,10 @@ - + - + + From d590aaeef61ee538e55bfdebf1bea1ccc1436865 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 11 Feb 2026 20:33:55 +0200 Subject: [PATCH 128/171] Use a default value for the block identifier --- src/aeonlib/salt/models/block_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 9ff3493..5ee7e6f 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -2,6 +2,7 @@ from __future__ import annotations +import uuid from typing import Annotated, Literal, Self import astropy.units as u @@ -103,7 +104,7 @@ class Block(BaseModel, validate_assignment=True): """ name: str - identifier: str | None = None + identifier: str = Field(default_factory=lambda: str(uuid.uuid4())) comments: str | None = None priority: Annotated[int, GreaterEqual(0), LessEqual(4)] ranking: Annotated[ From 3d07f6c01f7e47b4a1cff01fcbb35d010125b9c1 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 12 Feb 2026 17:52:00 +0200 Subject: [PATCH 129/171] Add an instrument name field to the instrument models --- src/aeonlib/salt/models/hrs_models.py | 5 ++++- src/aeonlib/salt/models/nirwals_models.py | 3 +++ src/aeonlib/salt/models/rss_models.py | 3 +++ src/aeonlib/salt/models/salticam_models.py | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py index 8cb47cc..cc53082 100644 --- a/src/aeonlib/salt/models/hrs_models.py +++ b/src/aeonlib/salt/models/hrs_models.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Annotated +from typing import Annotated, Literal from astropy import units as u from pydantic import ( @@ -28,6 +28,8 @@ class Hrs(BaseModel, validate_assignment=True): Parameters ---------- + instrument_name: + The instrument name, which is "HRS". This property is not serialized. num_cycles How often the exposure time patterns shall be executed. mode @@ -44,6 +46,7 @@ class Hrs(BaseModel, validate_assignment=True): The detector setup for the blue arm. """ + instrument_name: Literal["HRS"] = "HRS" num_cycles: PositiveInt = 1 mode: Annotated[HrsMode, LowerCaseValidator, UpperCaseSerializer] fibre_separation: Annotated[ diff --git a/src/aeonlib/salt/models/nirwals_models.py b/src/aeonlib/salt/models/nirwals_models.py index 8cac793..79127dc 100644 --- a/src/aeonlib/salt/models/nirwals_models.py +++ b/src/aeonlib/salt/models/nirwals_models.py @@ -41,6 +41,8 @@ class Nirwals(BaseModel, validate_assignment=True): Parameters ---------- + instrument_name: + The instrument name, which is "NIRWALS". num_cycles The number of times the dither pattern should be done. grating @@ -65,6 +67,7 @@ class Nirwals(BaseModel, validate_assignment=True): """ + instrument_name: Literal["NIRWALS"] = "NIRWALS" num_cycles: PositiveInt = 1 grating: Annotated[NirwalsGrating, LowerCaseValidator] grating_angle: Annotated[Angle, GreaterEqual(0 * u.deg), LessEqual(100 * u.deg)] diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 55283b1..61ac7a8 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -77,6 +77,8 @@ class Rss(BaseModel, validate_assignment=True): Parameters ---------- + instrument_name: + The instrument name, which is "RSS". num_cycles How often to cycle through the wave plate sequence. This is only relevant if you perform polarimetry. @@ -88,6 +90,7 @@ class Rss(BaseModel, validate_assignment=True): Dither pattern. """ + instrument_name: Literal["RSS"] = "RSS" num_cycles: PositiveInt = 1 configuration: ( RssImaging diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index 37a245a..3a3bc28 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -39,6 +39,8 @@ class Salticam(BaseModel, validate_assignment=True): Parameters ---------- + instrument_name: + The instrument name, which is "Salticam". num_cycles How often to cycle through the filter sequence. filter_sequence @@ -51,6 +53,7 @@ class Salticam(BaseModel, validate_assignment=True): Whether a nighttime flat should be taken for the observation. """ + instrument_name: Literal["Salticam"] = "Salticam" num_cycles: PositiveInt = 1 filter_sequence: Annotated[list[SalticamFilterSequenceStep], MinLen(1)] detector: SalticamDetector From 371e62a5ce40fd54310fd3b5227f6fafd6dc4214 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 12 Feb 2026 17:52:54 +0200 Subject: [PATCH 130/171] Fix the type of the instrument field --- src/aeonlib/salt/models/block_models.py | 7 +++++-- tests/salt/models/conftest.py | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 5ee7e6f..d6a488c 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -19,7 +19,7 @@ ) from aeonlib.models import Angle, Window -from aeonlib.salt.models import SaltSiderealTarget +from aeonlib.salt.models import SaltSiderealTarget, Salticam, Rss, Hrs, Nirwals from aeonlib.salt.models.util import LowerCaseValidator, CapitalizingSerializer from aeonlib.salt.models.types import ( PositiveDuration, @@ -30,6 +30,9 @@ from aeonlib.salt.validators import GreaterEqual, LessEqual, check_in_visibility_range +Instrument = Salticam | Rss | Hrs | Nirwals + + class Block(BaseModel, validate_assignment=True): """ A block for SALT. @@ -117,7 +120,7 @@ class Block(BaseModel, validate_assignment=True): windows: list[Window] | None = None target: SaltSiderealTarget acquisition: Acquisition - instrument: None + instrument: Instrument pool: str | None = None data_notification: Annotated[ Literal["normal", "fast"], LowerCaseValidator, CapitalizingSerializer diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 97fe8a7..d58ee6a 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -39,7 +39,7 @@ def base_request(base_block): @pytest.fixture() -def base_block(base_acquisition, base_constraints, base_target): +def base_block(base_acquisition, base_constraints, base_target, base_salticam): """A simple block to build or edit from.""" return Block( name="Test", @@ -49,7 +49,7 @@ def base_block(base_acquisition, base_constraints, base_target): constraints=base_constraints, target=base_target, acquisition=base_acquisition, - instrument=None, + instrument=base_salticam, ) From 700efda66a9d920c53b721e9a39c90a66b6d5a6e Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 12 Feb 2026 19:10:07 +0200 Subject: [PATCH 131/171] Fix handling of the minimum number of nights between visits --- tests/salt/models/conftest.py | 1 + tests/salt/models/test_block_models.py | 56 ++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index d58ee6a..86dfd11 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -46,6 +46,7 @@ def base_block(base_acquisition, base_constraints, base_target, base_salticam): priority=1, ranking="high", num_visits=1, + min_nights_between_visits=0, constraints=base_constraints, target=base_target, acquisition=base_acquisition, diff --git a/tests/salt/models/test_block_models.py b/tests/salt/models/test_block_models.py index 70ab0ac..ac495a4 100644 --- a/tests/salt/models/test_block_models.py +++ b/tests/salt/models/test_block_models.py @@ -7,6 +7,7 @@ from aeonlib.salt.models import Block, Acquisition from aeonlib.salt.models.block_models import ReferenceStar +from aeonlib.salt.models.util import render_template, validate_xml class TestBlock: @@ -30,15 +31,62 @@ def test_max_visits_and_visits( Test that the maximum number of visits must not be less than the number of visits. """ - block = base_block.model_dump() - block["num_visits"] = num_visits - block["max_num_visits"] = max_num_visits + block = base_block with expectation: - Block(**block) # type: ignore + block.num_visits = num_visits + block.max_num_visits = max_num_visits assert True + def test_salticam(self, base_block, base_salticam): + """Test that Salticam as a block instrument is handled correctly.""" + block = base_block + block.instrument = base_salticam + + xml = render_template("block.xml", block=block.model_dump()) + + assert "" in xml + + validate_xml(xml) + assert True + + def test_rss(self, base_block, base_rss): + """Test that RSS as a block instrument is handled correctly.""" + block = base_block + block.instrument = base_rss + + xml = render_template("block.xml", block=block.model_dump()) + + assert "" in xml + + validate_xml(xml) + assert True + + def test_hrs(self, base_block, base_hrs): + """Test that HRS as a block instrument is handled correctly.""" + block = base_block + block.instrument = base_hrs + + xml = render_template("block.xml", block=block.model_dump()) + + assert "" in xml + + validate_xml(xml) + assert True + + def test_nirwals(self, base_block, base_nirwals): + """Test that RSS as a block instrument is handled correctly.""" + block = base_block + block.instrument = base_nirwals + + xml = render_template("block.xml", block=block.model_dump()) + + assert "" in xml + + validate_xml(xml) + assert True + class TestConstraints: def test_constraints(self, base_constraints): From f0fc08930038f0e007a4ddd298d4fcff3c688732 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 12 Feb 2026 19:36:37 +0200 Subject: [PATCH 132/171] Add a template for generating blocks --- .../salt/models/serialize/templates/block.xml | 106 ++++++++++++++++++ tests/salt/models/serialize/test_templates.py | 71 ++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 src/aeonlib/salt/models/serialize/templates/block.xml diff --git a/src/aeonlib/salt/models/serialize/templates/block.xml b/src/aeonlib/salt/models/serialize/templates/block.xml new file mode 100644 index 0000000..654b8fd --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/block.xml @@ -0,0 +1,106 @@ + + {{ block.name }} + {{ block.identifier }} + {{ block.num_visits }} + + {{ block.min_nights_between_visits }} + days + + {% if block.comments %} + {{ block.comments }} + {% endif %} + {{ block.priority }} + {{ block.ranking }} + Science + true + {{ block.constraints.transparency }} + + {{ block.constraints.max_lunar_phase_percentage }} + % + + + {{ block.constraints.min_lunar_distance }} + degrees + + + + {{ block.constraints.max_seeing }} + arcseconds + + + 0.1 + arcseconds + + + + [SubBlock] + 1 + + [SubSubBlock] + 1 + + [Pointing] + + [Observation] + {% with acquisition=block.acquisition, target=block.target %} + {% include "acquisition.xml" %} + {% endwith %} + + [Telescope Configuration] + 1 + {% if block.acquisition.position_angle is not none %} + + {% if block.acquisition.position_angle != "parallactic" %} + {{ block.acquisition.position_angle }} + degrees + + {% if block.acquisition.do_not_flip_position_angle %} + true + {% else %} + false + {% endif %} + + {% else %} + + {% endif %} + + {% endif %} + + [Payload Configuration] + Science + Default + false + true + {% with instrument=block.instrument %} + {% if instrument.instrument_name == "Salticam" %} + {% with salticam=instrument %} + {% include "salticam.xml" %} + {% endwith %} + {% endif %} + {% if instrument.instrument_name == "RSS" %} + {% with rss=instrument %} + {% include "rss.xml" %} + {% endwith %} + {% endif %} + {% if instrument.instrument_name == "HRS" %} + {% with hrs=instrument %} + {% include "hrs.xml" %} + {% endwith %} + {% endif %} + {% if instrument.instrument_name == "NIRWALS" %} + {% with nirwals=instrument %} + {% include "nirwals.xml" %} + {% endwith %} + {% endif %} + {% endwith %} + + + + Normal + HTML + + + + + + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index d29ec79..74b66de 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -1,3 +1,5 @@ +from typing import Any + import astropy.units as u import pytest from astropy.coordinates import Angle @@ -482,3 +484,72 @@ def test_acquisition( validate_xml(xml) assert True + + +class TestBlock: + @pytest.mark.parametrize("full", [False, True]) + def test_block(self, full: bool, base_block): + """Test that the block template generates valid XML.""" + block = base_block + + if full: + block.comments = "some comment" + block.acquisition.position_angle = 67 * u.deg + else: + block.comments = None + block.acquisition.position_angle = None + + xml = render_template("block.xml", block=block.model_dump()) + + if full: + assert "" in xml + assert "" in xml + + validate_xml(xml) + assert True + + @pytest.mark.parametrize( + "position_angle, do_not_flip, expected, not_expected", + [ + ( + 17 * u.deg, + True, + ["OnSkyPositionAngle", "Fixed"], + ["UseParallacticAngle"], + ), + ( + "parallactic", + False, + ["OnSkyPositionAngle", "UseParallacticAngle"], + ["Fixed"], + ), + ( + None, + True, + [], + ["OnSkyPositionAngle", "Fixed", "UseParallacticAngle"], + ), + ], + ) + def test_position_angle( + self, + position_angle: Any, + do_not_flip: bool, + expected: list[str], + not_expected: list[str], + base_block, + ): + """Test that the position angle is handled correctly in the generated XML.""" + block = base_block + block.acquisition.position_angle = position_angle + block.acquisition.do_not_flip_position_angle = do_not_flip + + xml = render_template("block.xml", block=block.model_dump()) + + for e in expected: + assert e in xml + for ne in not_expected: + assert ne not in xml + + validate_xml(xml) + assert True From 3dabfc6eb88d9f836ba8d0524c9aba5a1a106ed5 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 13 Feb 2026 09:46:08 +0200 Subject: [PATCH 133/171] Add a template for generating the XML for al, request blocks --- .../salt/models/serialize/templates/blocks.xml | 5 +++++ tests/salt/models/serialize/test_templates.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/aeonlib/salt/models/serialize/templates/blocks.xml diff --git a/src/aeonlib/salt/models/serialize/templates/blocks.xml b/src/aeonlib/salt/models/serialize/templates/blocks.xml new file mode 100644 index 0000000..035442d --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/blocks.xml @@ -0,0 +1,5 @@ + + {% for block in blocks %} + {% include "block.xml" %} + {% endfor %} + diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 74b66de..22ff537 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -1,3 +1,5 @@ +import re +from copy import deepcopy from typing import Any import astropy.units as u @@ -553,3 +555,16 @@ def test_position_angle( validate_xml(xml) assert True + + +class TestBlocks: + def test_blocks(self, base_request): + request = base_request + block = base_request.blocks[0] + block_copy = deepcopy(block) + request.blocks.append(block_copy) + + xml = render_template("blocks.xml", blocks=request.model_dump()["blocks"]) + + assert "" in xml + assert len(re.findall(r" Date: Fri, 13 Feb 2026 13:12:04 +0200 Subject: [PATCH 134/171] Add a method for getting the set of attachments used by a request --- src/aeonlib/salt/models/request_models.py | 29 ++++++++ tests/salt/data/dummy_finder_chart_2.pdf | Bin 0 -> 8449 bytes tests/salt/models/test_request_models.py | 87 ++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 tests/salt/data/dummy_finder_chart_2.pdf diff --git a/src/aeonlib/salt/models/request_models.py b/src/aeonlib/salt/models/request_models.py index 4eff331..f8e0b78 100644 --- a/src/aeonlib/salt/models/request_models.py +++ b/src/aeonlib/salt/models/request_models.py @@ -22,3 +22,32 @@ class Request(BaseModel, validate_assignment=True): proposal_code: str blocks: Annotated[list, MinLen(1)] + + def attachments(self) -> set[pathlib.Path]: + """ + Return the set of attachments for this request. + + All the attachment paths are reso,lved using the `resolve` method of the + `pathlib.Path` class. + + Returns + ------- + The set of attachments. + """ + _attachments: set[pathlib.Path] = set() + + for block in self.blocks: + for finder_chart in block.acquisition.finder_charts: + if not isinstance(finder_chart, pathlib.Path): + raise ValueError("The finder chart value is not a Path instance.") + _attachments.add(finder_chart) + + if block.instrument.instrument_name == "RSS": + if hasattr(block.instrument.configuration, "mask"): + mask = block.instrument.configuration.mask + if not isinstance(mask, pathlib.Path): + raise ValueError("The mask value is not a Path instance.") + _attachments.add(mask) + + # Remove duplicates + return set(a.resolve() for a in _attachments) diff --git a/tests/salt/data/dummy_finder_chart_2.pdf b/tests/salt/data/dummy_finder_chart_2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3a6e3e757b6a2198fc0d14d8e71f561426c22202 GIT binary patch literal 8449 zcmcIqXIN8Pv!*E>qzDKI5vfu_AOWI+^dca=N-=~0p&LR|X-bhUO`3@Gjz|%tN$-L* z0TGlg(xo@KJ9y4_Jng&p&rNu;_nI|pX7;Ri?`PiGaB0db3POZL$+%kPzRxY?Y`%Ek z(ncl<5(YV0*pgkp4gxEp?Xf5faI{BYQSvCHlO+lSRzo>jV{Jg9BEldkDKZx<28D1S z^T3aL^3Jwd_?-WmcL@ArP**h4h}K_vX7>GnwA>lVShlc?C-mc-ugSgo%?<@j+e!DA z(qstG2zR&oF^dR6iHm5?P$*U)K`i&8nbsvW7Qb4*(G>2SdM`N@!p=0_>ZY0*HD_db zn3m>@(VHgPDbYYV|FSKFEGC1dEe2a@MrBwSF8qX4Rn3y4#kdTgk<1p9Fx}GoqUxs%ZW@}d=wWm>28RFlJt(yi+(_TTVio85~F7;Frd z&SFK0Jc7JhhN8#UQ89YgA`WYhd&rjRjr+`t9}&J63?wp8i}AdjEs`{`%BbK|n#&%hGDDghzYkyn8$$?ukXpwAp-{bO}OJEO&~z^jb1S459;& zN{@xo_QCGeX)L^PzLe|LxbXyn@z~C?;5ebk46e1)6Hzfo1?>BF_)xYn$ zCdLV=gTjJt11{wiL10~!2Nq-s0?RqsJ7IL35l9pW2M#$G2ncrSL`n*P2;lcrtg8zF ziQ&Y6bsRu`b0G1HgIhpwU>O{=906Q`!15?JG!mt)B>Nw5iHJc&{uzG1LN4X0yROa{ zLr`Cxc-?zi4iMKC^F30~z%}LbL|VZrzM2Gq{@iZzXB6pa`1CDx8}OBaIFJhTbRR4193n5*1YwzDeaYgscc@K%%C<->_j6o4h9KEidhNa_0GKIuqi#XVaLC zdN0&MpNG|GP9||pWrPu>nIv$1vCeovjH=UBNy3}k=LlwFYw|S1|k!?L2LKLiGkxes_fS&u>VRf)krn7GW;g4-rpL*<} z*0)to5h2Xz`_k#D0zQ|KL!wR8N)hYIiqVI8oBg+ApV_>1jwL(p9qMgxFZVr{343nz z*?zeYT*rX7!)cs+v8+B$53k(etn}Pep~e~r@B10N^2ukLY=QP>(Jj*yJmjIv%l0n# zsdhsAU&RkoA3Alt4TSF|cCQ}lU(05^;dfc(yeLXxE;}b%RlcW(f-1hx@_6^$$xH(r z{jRfF;8|(|<3_+&J*}^?r2fG+H38TC44(*DlKJ}#Px&y3@ep6|PO+o6aW%*D3V>@? zsStNKN#e;}CggOyagV@j>GnAYSx0@XO;OZKmq?L&KNb$y$xT8Wr_^LPbmn&`A}ujo z=>iurKKt7@63{*&#=anf`UwdW2;8A$^%Zp{oCM+d`zoI!d`c*!On{aqDwUBM#Uqn} zjN-dJvUSEA^1Jt*=+;9q=d&h61`U+X1k_erS@1>*vm=%j7>{$fQn` z{cw)On8Z^~MpJ^GuvISj$+>9h{rB9tOjjP|r^#tbZe7MQSCg1NI(R|+9t1smjPK=7 zlRz+tpd7_}?K@U-jg>^Hsr!q($N6W(j1A*cH>((=s4ANnr&!kU1Om*PVo0PrNC^8C z4M|uwh3Jz*`PE;0uw{qg_$6uuqg|D#0QI; z!ovy31bsEec`>VVqJH}ivn+;RWgGGo-f$xoqe%`IY|+0HdFOUX{42sy#PT&(s`iEp z)2z#*kJ>Af^lJ~P8(r2d9$cy?^I`R2`y9#M_Lk=f zsWFk=L+VRUL7euSy62cl7D$f$X_}x~ez_W0j=*!CEhZLxuOdceYE@XdZ^h871adNJ zbMhsY`{}CKa+q>Ua@Spo>AY&Ggkdt0Hq$8POa+;9QAgjiA`p=%*=8l{ZNSs3;_*$5}kKNU{jFcr*=b)#9p+_KU8h)yR`x zEE(X+y2LCynB_T8Rjyus^&{$|@I@u@%)`N$#nvtMuXS6bC@$3PIo>(eIp(>tF72~T z!guyWwJ&6a#e~&_MQ%dp-}KG&jf=fRo1N71#qD2lyz(H9BhD%gKQ1y~P0wkVemEuH zAzx!i*p}NCVLN7fI4o2U-j~zo{=6^69C|W)6!fj6I{04If$etlH_2}?%t_4k%omvT znB$>sunS3!Nu5b^Nej^SavNjv3L@h<wooUe1py=)aO-oN=v90{QxG$cEI+ZSIob6qRU|@J@A|(V&>2+uPDkB{OtC2#p_yzk^IiFLEdii=Odo#!>7}ptx1eq}X zcc-dYF;PO6LSg0}&S^t><8d>_O9wsbBjF2fEeIn znj2vnZNe~s1 zekIE!QY6wRmLeA+r9IQSo4q_JYddgGQ>b2dnD%;aGexJVS4_+g-J*NTFAZZ zS*7RU?cq)&XrUl4*HWV#*)hrYk*bF|=y#Bm*X#euP@~qAuLzjg&tnjqf3|Wk5c1x zF|w*ji1?wltsKUg%=O5Kyv(+A(pm|X+NIS)Gru*PH0#)Xv2(NY&>Ln2b9c3_cl!}| z)CgO(qbdscP;4^(=9ar@WA)T_{Zga@$U%8*y<4$69L4h4F=TQ`>xEWH{T-XKwfQ#L zH?mq&b%MROsq9mJD^>15rPlrrvaD9qi`fTK7us_kW6>N{nNx^^`>E>g^}6V`{FOAMRPsh)4x~N7%bQ8BW~ZlLyeBKj4T()Ev>ux(pG6W z=Ue7CKjz4!`MPiSD_2rJwvUgz{p{c~h#yYaN-V`j&syu#v8!oUB;(ME_%v16+_#ke zt@^FCkGYThit+rRq5%FB{Sh1$?=(blgciWwgzii9hrz=?06wP?E zM-WYX#%UiueF%P?DTcnUcK`FD?_kQs-iv}a zl20CNh3zCJm33LG_C@ucf0hhSb@%=*+ZM198q64U)c`A1T|PUIQe09z+3gqq(rn`m zCU3m%@JKJdz`7W2dd(}vt8OLmIQ#=!&GV<`%iie4iZzG2Mdz~Pr!z$n^=G^m4onZh zlRMR3a}g&gEQFzF4_1Zh@1K}`l3etfJnkpXqc*$Ib5G*f<`}s=lg>k?apigbjiTe> zy%DRtx~=6GBX|1B9$QBSR>^@+;`U!^G@t3q>*H4}SNx__q|_I-A2xCC)mH0zcUb)3 zOu$>dAFp@g`U_IM-4E-mEjnA)?fRDzZ`_>Bt%o0c*l}2}{5m`6j$X^o37IUYzp>+Y zoOX!ZYL8l6*4WdCOAWu_{($u8y9c*})%@pO|INd{m)W=q7FYNJ#jc8+oGiixWeLL7 zs@fpa-%D2ru5$jpIR06R>tHdiNUSafh0=C%!h*o6Ao%GTphm}4=eP+F1XsZW6}XE0 z&*5ps{zpNKTkuat73@(CAg~Tl6+0na9dM;SSQB&_0$@uOD9Rx}D|=JWFV*#_?7v#m z#F_a$NNdnpp|EHq;uZ>s z>y-G&qg;>}v@_NTLk9UtiNFeu09!^o0@tUGKx#WVARPaYD55bgSUDR61|ZP@R0nBLrGQC=n3klw3OO=Azy@B?WiSd+_V=FhePMy97c2UfW z0*N+T>l=@FRD232092c85AM+6%Vru2(Z6Dl4Z+xN2 zxMluAKp?F#H?)X1L$zH)(t1eHM+v^Y72G_$`iK{nIzwadzM}nD;Cq^{g zo?YKq&a+L~R!ezc_3EQ}tY#`~a$=hgRAa+xH={IgW86sC{j11Z;#qMa6Q6X?b)8Ge zqe9gpKR6k;;+I~Y?1e6FKUtAS#aaJnhu9d&EBX6-%Wv8IM05-H0#vViN&4A`sBpv^>if$%7bG5E= zwyp=Jl|K1FW@cP-_tf0tIYciM(M-QxDI8?EdZCr2(A&%YqN($(EvNC)F9E;hMswgX0T-3!guC(>gn(OyM{ zoaeW|x(Azw+vcaTK=pJGN}dGOOFE)1SsVgdW3-3yyjj6Dybie+Lh8eXU&`c^FWRLK zZGd9MZk)^NP0C8sYse$lp`>l0urLmt>SNmshXj@*qZV4xy-JkK1`)m5_BQj64`Wf27^$uD0`zXsPLCqt?*e zps4QEcRn`7l8Tc>dMEsi9iOO0TEcEIlt!zsnFJlRM_#nS8(rnbXo_~7thMiRZO++t z)2sJiI(WY4XBclLmZ%Ek@9==3 zq((Tw5Io9m#J5Ke3-x-azo7R_MPT?dCo5YkEyla8FMMYTQ_|y*P1%idt0NH{)P+f8>DLG$rdoH9m>~4bi%dVOh_K`jnamVv+ zs(qrw{o|=O-Bhw2KMJuSsV1|fdM4zhlue)1KDjb`hMi#@(HYkHxJc?d-rW~?Y6Ou4 z8u&SW)d3LlI=m?Ykf3fZHIaYggx<4*o)?VUtNFTV0$Wy%o{aMexp!HRSCF~}Gy2nK zKN;Fmx_;7Zr>w;UTa*^Om4$qz^0iNJW^3Xq7HIf-l|%>Eiy-Wfm z6D=!vR|g8^6fHAlWRhs5!6k&F#H?rPDZkoD5>Ih}1Pd)p1Gx%fi^ibkA5y}NDwMCU zoipYXoU5c#t!vIc`cR&!P$=dw8oPv!rypDC+}`45WoVrcn!6X6RcpD+IpqIZ^S$E0 zkDcYm`Qcb%`ieo*1d(^D{f%R>`pcoCpD5pT_ZA(G56FzMySh`hnQH447Vyu9ZEf$3{ni1u$R2gK^~KCfLr_( zt@Rt#9d|TRb*5>8qXJ}4+mhA%NN@XgFgNUf$*#vV*?oG4uMZ);PPSfm0(GXUBB>=X!7nBp zj-%6$;G;2Tv#F3PxM*y&BeSD7C)8nD!l6Q=y`?0eDTz;&?bN}*`?8fEAwgc-)O;uCOfu_@1T1uV=CYmvQfr4(jnM#bRaQbS5 zDW%INVRe}xN~(zx^E-O}r8Fqy*?OmJ8;6Sy>r(EH&C>WV0dj1_cf1Jv;Zdf^{LTBr z1Wrsu3fIKcWR5V$)X_Z84Vw4;-Pa}qEz<0&nz>qbxw3YpA?9qMH`AsFo3nH*?C#e$ zFR9Z{c5D-g$VK&N=ezd@+6fa+#A-tUy_eH+98gAqsrG)4#`JCOEu_?ImL;kgs*=kaUXW8v^ zx8SnU$F_ug=H|NxeXWzl78(u3xl)%NZF2NfQ?Tq8Hdzv=vtU(9dMLURuJX5sDy-cR zkaM2zvy<@~1NZf*20eYs;OG?rR@c0%l;b|=i7X5j3EXb=6r<3Y8T)dxjZJ7$(n{2T z+c3R>gUW<@ll01+k@&=F@C`A+x4|jao6(kUw)g9XuKyrupWa)+N0VFh2YDUc3m8b~ zZx?N%kuq##n~C0sG;5(ds4_1&ZoYW;NNmS(Nu4t<@=K);o&@-)O043WPs-jhakig` zGlc}__j(^T3b+y+H$XMdM&MifZZ>dz zfql8MeyQPscti3tM78D>X~NmY)fe8W!l?ad{Qp%Ty)ftoDUPzoDc7~cU-(x&YMCL33UqDT2oU9sWa|VLua=$s=gU7 zCH)MpZog*wZevQVP|0Mh$WH4*$PlDN<#v#q$--kP2-@Ad`hDd= zih}g?VX%k6LDBOCSuEoGg;C`KN<%WAH`AyMW6$T|h3(=xJ2iw{pFhE=swiIcdg;~c zeXpoz`&2$1FRVX679dtXAop5^N}q?!0xySB|GjVYlDgf4^2E;U1uTrmZr{Xg=2-~G z+X`R)VDZ$piC$*XA01K3xoJgdkRb_pokppEK#@7XM_M0`#&F$7z`jPAS=-CGAOQVf%||Qf0v1h z2?Lz>&oW_Q91s4B3?}mTwK!J%7d=r4z~f(K;y4cd7kgqNKvU( Date: Fri, 13 Feb 2026 16:39:55 +0200 Subject: [PATCH 135/171] Fix the RSS spectroscopy template --- .../salt/models/serialize/templates/rss_spectroscopy.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/serialize/templates/rss_spectroscopy.xml b/src/aeonlib/salt/models/serialize/templates/rss_spectroscopy.xml index b7c683b..db80269 100644 --- a/src/aeonlib/salt/models/serialize/templates/rss_spectroscopy.xml +++ b/src/aeonlib/salt/models/serialize/templates/rss_spectroscopy.xml @@ -8,7 +8,7 @@ {% if configuration.mask %} - A{{ configuration.mask.absolute() }} + {{ configuration.mask.absolute() }} {% endif %} From aacf450e5af5f8a6dc910638ab693cda2b54a638 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 13 Feb 2026 16:43:48 +0200 Subject: [PATCH 136/171] Fix the acquisition template --- src/aeonlib/salt/models/serialize/templates/acquisition.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/serialize/templates/acquisition.xml b/src/aeonlib/salt/models/serialize/templates/acquisition.xml index 83cc94e..0c0cf75 100644 --- a/src/aeonlib/salt/models/serialize/templates/acquisition.xml +++ b/src/aeonlib/salt/models/serialize/templates/acquisition.xml @@ -36,7 +36,7 @@ - {% for finder_chart in finder_charts %} + {% for finder_chart in acquisition.finder_charts %} {{ finder_chart }} From 984bc06d297ae8085882a178931f98443cca931f Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Fri, 13 Feb 2026 16:45:02 +0200 Subject: [PATCH 137/171] Add a utility function for updating the attachment paths in an XML document --- pyproject.toml | 1 + src/aeonlib/salt/models/util.py | 47 ++++++++++ tests/salt/models/serialize/test_util.py | 107 ++++++++++++++++++++++- uv.lock | 24 +++++ 4 files changed, 178 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2b212c2..e0f0a4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ lt = [ "suds>=1.2.0", ] salt = [ + "beautifulsoup4>=4.14.3", "jinja2>=3.1.6", "lxml>=6.0.1", ] diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index cc13b3b..064d4f5 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -7,6 +7,7 @@ import astropy.units as u from astropy.coordinates import Angle +from bs4 import BeautifulSoup from jinja2 import Environment, PackageLoader, select_autoescape, BaseLoader from lxml import etree from pydantic import PlainSerializer, BeforeValidator @@ -232,3 +233,49 @@ def _upper(s: str | None) -> str | None: This serializer is only intended for use in the serialization of SALT data models. """ + + +def replace_attachment_paths(xml: str, replacements: dict[pathlib.Path, str]) -> str: + """ + Replace the attachment paths in the given XML. + + Parameters + ---------- + xml + XML. + replacements + Dictionary of attachment paths and their replacements. + + Returns + ------- + The XML with the attachment paths updated. + + Raises + ------ + ValueError + If an attachment is missing in the dictionary of replacements. + """ + + # Resolve all attachment paths + replacements_resolved = {k.resolve(): v for k, v in replacements.items()} + + # Check whether there are duplicate keys or values + if len(set(replacements_resolved.keys())) != len(replacements): + raise ValueError( + "Two or more keys of the replacements dictionary resolve to the same path." + ) + if len(set(replacements_resolved.values())) != len(replacements_resolved.values()): + raise ValueError("There duplicate values in the replacements dictionary.") + + # Replace the attachment paths + soup = BeautifulSoup(xml, "xml") + for path_element in soup.find_all("Path"): + path_text = path_element.text.strip() + path = pathlib.Path(path_text).resolve() + if path not in replacements_resolved: + raise ValueError( + f"Path missing in replacements dictionary: {path_text} (resolved: {str(path)}" + ) + path_element.string = replacements_resolved[path] + + return soup.prettify() diff --git a/tests/salt/models/serialize/test_util.py b/tests/salt/models/serialize/test_util.py index ddec98e..f16635b 100644 --- a/tests/salt/models/serialize/test_util.py +++ b/tests/salt/models/serialize/test_util.py @@ -1,9 +1,14 @@ import pathlib +import re import pytest from jinja2 import FileSystemLoader -from aeonlib.salt.models.util import validate_xml, render_template +from aeonlib.salt.models.util import ( + validate_xml, + render_template, + replace_attachment_paths, +) def test_validate_non_well_formed_xml(): @@ -53,3 +58,103 @@ def test_render_template_with_escaping(): loader = FileSystemLoader(pathlib.Path(__file__).parent.parent.parent / "data") rendered = render_template("test.xml", loader, a="a >= 1 & a <= 5", b=2) assert "a >= 1 & a <= 5" in rendered + + +def test_replace_attachment_paths( + base_request, base_block, base_rss, base_rss_multi_object_spectroscopy +): + """Test replacing attachment paths.""" + # finder_chart deliberately includes ".." to test resolution. + finder_chart = ( + pathlib.Path(__file__).parent.parent.parent + / "data/../data" + / "dummy_finder_chart.pdf" + ) + mos_mask = ( + pathlib.Path(__file__).parent.parent.parent / "data" / "dummy_rss_mos_mask.rsim" + ) + request = base_request + block = base_block + rss = base_rss + configuration = base_rss_multi_object_spectroscopy + configuration.mask = mos_mask + block.acquisition.finder_charts = [finder_chart] + rss.configuration = configuration + block.instrument = rss + request.blocks = [block] + + xml = render_template("blocks.xml", blocks=request.model_dump()["blocks"]) + + replacements = { + finder_chart.resolve(): "Included/FinderChart.pdf", + mos_mask.resolve(): "Included/MOS.rsmt", + } + + updated_xml = replace_attachment_paths(xml, replacements) + + assert re.search(r"\s*Included/FinderChart.pdf\s*", updated_xml) + assert re.search(r"\s*Included/MOS.rsmt\s*", updated_xml) + + +def test_missing_replacement(base_request, base_block): + """Test that an error is raised if an attachment path replacement is missing.""" + finder_chart = ( + pathlib.Path(__file__).parent.parent.parent / "data" / "dummy_finder_chart.pdf" + ) + request = base_request + block = base_block + block.acquisition.finder_charts = [finder_chart] + request.blocks = [block] + + xml = render_template("blocks.xml", blocks=request.model_dump()["blocks"]) + with pytest.raises(ValueError, match="Path missing"): + replace_attachment_paths(xml, {}) + + +def test_duplicate_replacement_key(base_request): + """ + Test that an error is raised if there is a duplicate key in the dictionary of + replacements. + """ + # file_1a and file_1b are the same file + file_1a = ( + pathlib.Path(__file__).parent.parent.parent / "data" / "dummy_finder_chart.pdf" + ) + file_1b = ( + pathlib.Path(__file__).parent.parent.parent + / "data/../data" + / "dummy_finder_chart.pdf" + ) + + request = base_request + + xml = render_template("blocks.xml", blocks=request.model_dump()["blocks"]) + + replacements = {file_1a: "Included/File_1a.pdf", file_1b: "Included/File_1b.pdf"} + + with pytest.raises(ValueError, match="same path"): + replace_attachment_paths(xml, replacements) + + +def test_duplicate_replacement_value(base_request): + """ + Test that an error is raised if there is a duplicate value in the dictionary of + replacements. + """ + file_1 = ( + pathlib.Path(__file__).parent.parent.parent / "data" / "dummy_finder_chart.pdf" + ) + file_2 = ( + pathlib.Path(__file__).parent.parent.parent + / "data" + / "dummy_finder_chart_2.pdf" + ) + + request = base_request + + xml = render_template("blocks.xml", blocks=request.model_dump()["blocks"]) + + replacements = {file_1: "Included/File.pdf", file_2: "Included/File.pdf"} + + with pytest.raises(ValueError, match="duplicate value"): + replace_attachment_paths(xml, replacements) diff --git a/uv.lock b/uv.lock index 7e6cc6b..c7f6329 100644 --- a/uv.lock +++ b/uv.lock @@ -23,6 +23,7 @@ lt = [ { name = "suds" }, ] salt = [ + { name = "beautifulsoup4" }, { name = "jinja2" }, { name = "lxml" }, ] @@ -39,6 +40,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "astropy", specifier = ">=7.0.1" }, + { name = "beautifulsoup4", marker = "extra == 'salt'", specifier = ">=4.14.3" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "jinja2", marker = "extra == 'salt'", specifier = ">=3.1.6" }, { name = "lxml", marker = "extra == 'lt'", specifier = ">=5.4.0" }, @@ -119,6 +121,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/03/8c/a556b71e4603d6156814da968b7cefa9bd882e59dc691168584418f4de77/astropy_iers_data-0.2025.9.15.0.37.0-py3-none-any.whl", hash = "sha256:4ea19813150f2d6dfb0257c5e96d14ae9eac6e5a80af27ec79e3aaef8aadfa93", size = 1964242, upload-time = "2025-09-15T00:37:44.298Z" }, ] +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + [[package]] name = "certifi" version = "2025.8.3" @@ -802,6 +817,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + [[package]] name = "suds" version = "1.2.0" From 821c025a08efd49983b059d5763eab4c1553f489 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 16 Feb 2026 08:59:38 +0200 Subject: [PATCH 138/171] Rename one of the test finder charts --- ...der_chart.pdf => dummy_finder_chart_1.pdf} | Bin tests/salt/models/conftest.py | 2 +- tests/salt/models/serialize/test_util.py | 32 +++++++++-------- tests/salt/models/test_request_models.py | 33 ++++++++++-------- 4 files changed, 38 insertions(+), 29 deletions(-) rename tests/salt/data/{dummy_finder_chart.pdf => dummy_finder_chart_1.pdf} (100%) diff --git a/tests/salt/data/dummy_finder_chart.pdf b/tests/salt/data/dummy_finder_chart_1.pdf similarity index 100% rename from tests/salt/data/dummy_finder_chart.pdf rename to tests/salt/data/dummy_finder_chart_1.pdf diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 86dfd11..b7056a2 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -86,7 +86,7 @@ def base_constraints(): @pytest.fixture() def base_acquisition(): finder_chart = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart.pdf" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" ) return Acquisition(finder_charts=[finder_chart], position_angle=45 * u.deg) diff --git a/tests/salt/models/serialize/test_util.py b/tests/salt/models/serialize/test_util.py index f16635b..182e8f9 100644 --- a/tests/salt/models/serialize/test_util.py +++ b/tests/salt/models/serialize/test_util.py @@ -61,17 +61,18 @@ def test_render_template_with_escaping(): def test_replace_attachment_paths( - base_request, base_block, base_rss, base_rss_multi_object_spectroscopy + base_request, base_block, base_rss, base_rss_multi_object_spectroscopy ): """Test replacing attachment paths.""" # finder_chart deliberately includes ".." to test resolution. finder_chart = ( - pathlib.Path(__file__).parent.parent.parent - / "data/../data" - / "dummy_finder_chart.pdf" + pathlib.Path(__file__).parent.parent.parent + / "data/../data" + / "dummy_finder_chart_1.pdf" ) mos_mask = ( - pathlib.Path(__file__).parent.parent.parent / "data" / "dummy_rss_mos_mask.rsim" + pathlib.Path( + __file__).parent.parent.parent / "data" / "dummy_rss_mos_mask.rsim" ) request = base_request block = base_block @@ -99,7 +100,8 @@ def test_replace_attachment_paths( def test_missing_replacement(base_request, base_block): """Test that an error is raised if an attachment path replacement is missing.""" finder_chart = ( - pathlib.Path(__file__).parent.parent.parent / "data" / "dummy_finder_chart.pdf" + pathlib.Path( + __file__).parent.parent.parent / "data" / "dummy_finder_chart_1.pdf" ) request = base_request block = base_block @@ -118,12 +120,13 @@ def test_duplicate_replacement_key(base_request): """ # file_1a and file_1b are the same file file_1a = ( - pathlib.Path(__file__).parent.parent.parent / "data" / "dummy_finder_chart.pdf" + pathlib.Path( + __file__).parent.parent.parent / "data" / "dummy_finder_chart_1.pdf" ) file_1b = ( - pathlib.Path(__file__).parent.parent.parent - / "data/../data" - / "dummy_finder_chart.pdf" + pathlib.Path(__file__).parent.parent.parent + / "data/../data" + / "dummy_finder_chart_1.pdf" ) request = base_request @@ -142,12 +145,13 @@ def test_duplicate_replacement_value(base_request): replacements. """ file_1 = ( - pathlib.Path(__file__).parent.parent.parent / "data" / "dummy_finder_chart.pdf" + pathlib.Path( + __file__).parent.parent.parent / "data" / "dummy_finder_chart_1.pdf" ) file_2 = ( - pathlib.Path(__file__).parent.parent.parent - / "data" - / "dummy_finder_chart_2.pdf" + pathlib.Path(__file__).parent.parent.parent + / "data" + / "dummy_finder_chart_2.pdf" ) request = base_request diff --git a/tests/salt/models/test_request_models.py b/tests/salt/models/test_request_models.py index 4c6b6d8..6fd8403 100644 --- a/tests/salt/models/test_request_models.py +++ b/tests/salt/models/test_request_models.py @@ -19,7 +19,7 @@ def test_no_blocks(self): assert exc_info.value.errors()[0]["type"] == "too_short" def test_no_attachments( - self, base_request, base_block, base_rss, base_rss_longslit_spectroscopy + self, base_request, base_block, base_rss, base_rss_longslit_spectroscopy ): """Test the case that the request includes no attachments.""" request = base_request @@ -34,17 +34,20 @@ def test_no_attachments( assert request.attachments() == set() def test_multiple_attachments( - self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy + self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy ): """Test the case that the request includes multiple attachments.""" finder_chart_1 = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart.pdf" + pathlib.Path( + __file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" ) finder_chart_2 = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" + pathlib.Path( + __file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" ) mos_mask = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_rss_mos_mask.rsim" + pathlib.Path( + __file__).parent.parent / "data" / "dummy_rss_mos_mask.rsim" ) request = base_request block = base_block @@ -63,25 +66,27 @@ def test_multiple_attachments( } def test_duplicate_attachments( - self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy + self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy ): """Test the case that the request uses the same attachment multiple times.""" # finder_chart_1a, finder_chart_1b and finder_chart_1c denote the sane file finder_chart_1a = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart.pdf" + pathlib.Path( + __file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" ) finder_chart_1b = ( - pathlib.Path(__file__).parent.parent - / "data/../data" - / "dummy_finder_chart.pdf" + pathlib.Path(__file__).parent.parent + / "data/../data" + / "dummy_finder_chart_1.pdf" ) finder_chart_1c = ( - pathlib.Path(__file__).parent.parent - / "data/../../salt/data" - / "dummy_finder_chart.pdf" + pathlib.Path(__file__).parent.parent + / "data/../../salt/data" + / "dummy_finder_chart_1.pdf" ) finder_chart_2 = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" + pathlib.Path( + __file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" ) mos_mask = finder_chart_1c # as we are testing for duplicates request = base_request From 67578f8adf8d0a639bb7a03cd3cb68861f65246f Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 16 Feb 2026 09:36:26 +0200 Subject: [PATCH 139/171] Add a fixture for generating test data files --- tests/salt/models/conftest.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index b7056a2..40711e5 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -1,4 +1,5 @@ import pathlib +import uuid import astropy.coordinates import pytest @@ -86,7 +87,7 @@ def base_constraints(): @pytest.fixture() def base_acquisition(): finder_chart = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" ) return Acquisition(finder_charts=[finder_chart], position_angle=45 * u.deg) @@ -237,3 +238,21 @@ def base_nirwals_dither_pattern_step(): gain="faint", sampling="up-the-ramp", ) + + +@pytest.fixture() +def create_test_binary_file(tmp_path: pathlib.Path): + """ + Return a function for generating test files. + + The returned function accepts content (as bytes, such as b"I'm a test file." and a + file extension (such as ".pdf"), generates a temporary file with the given content + and file extension, and returns the path to the generated file. + """ + + def _create_file(content: bytes, extension: str) -> pathlib.Path: + file_path = tmp_path / f"{str(uuid.uuid4())}{extension}" + file_path.write_bytes(content) + return file_path + + return _create_file From 77316c7e93f467d87fff937a5f1bd0a4d0070d57 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 16 Feb 2026 13:53:11 +0200 Subject: [PATCH 140/171] Validate the XML as well --- tests/salt/models/serialize/test_templates.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 22ff537..330b7a8 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -568,3 +568,6 @@ def test_blocks(self, base_request): assert "" in xml assert len(re.findall(r" Date: Mon, 16 Feb 2026 13:54:30 +0200 Subject: [PATCH 141/171] Parse the XML as bytes You get an error you parse the XML as a string with encoding information. --- src/aeonlib/salt/models/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index 064d4f5..8f72404 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -92,7 +92,7 @@ def validate_xml(xml: str) -> None: _load_schema() try: - xml_doc = etree.parse(io.StringIO(xml)) + xml_doc = etree.parse(io.BytesIO(xml.encode("utf-8"))) _schema.assertValid(xml_doc) except (etree.DocumentInvalid, etree.XMLSyntaxError) as e: raise ValueError(str(e)) From 39ec5094eadcd1d97b5c43129407ad1024b0c58b Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 16 Feb 2026 14:23:45 +0200 Subject: [PATCH 142/171] Fix finder chart name and formatting --- tests/salt/models/test_request_models.py | 33 ++++++++++-------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/tests/salt/models/test_request_models.py b/tests/salt/models/test_request_models.py index 6fd8403..af83342 100644 --- a/tests/salt/models/test_request_models.py +++ b/tests/salt/models/test_request_models.py @@ -19,7 +19,7 @@ def test_no_blocks(self): assert exc_info.value.errors()[0]["type"] == "too_short" def test_no_attachments( - self, base_request, base_block, base_rss, base_rss_longslit_spectroscopy + self, base_request, base_block, base_rss, base_rss_longslit_spectroscopy ): """Test the case that the request includes no attachments.""" request = base_request @@ -34,20 +34,17 @@ def test_no_attachments( assert request.attachments() == set() def test_multiple_attachments( - self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy + self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy ): """Test the case that the request includes multiple attachments.""" finder_chart_1 = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" ) finder_chart_2 = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" ) mos_mask = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_rss_mos_mask.rsim" + pathlib.Path(__file__).parent.parent / "data" / "dummy_rss_mos_mask.rsim" ) request = base_request block = base_block @@ -66,27 +63,25 @@ def test_multiple_attachments( } def test_duplicate_attachments( - self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy + self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy ): """Test the case that the request uses the same attachment multiple times.""" # finder_chart_1a, finder_chart_1b and finder_chart_1c denote the sane file finder_chart_1a = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" ) finder_chart_1b = ( - pathlib.Path(__file__).parent.parent - / "data/../data" - / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent + / "data/../data" + / "dummy_finder_chart_1.pdf" ) finder_chart_1c = ( - pathlib.Path(__file__).parent.parent - / "data/../../salt/data" - / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent + / "data/../../salt/data" + / "dummy_finder_chart_1.pdf" ) finder_chart_2 = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" ) mos_mask = finder_chart_1c # as we are testing for duplicates request = base_request From 5f14a06552d9acd5bc6a05f92ab34df5fafcd869 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 16 Feb 2026 17:56:32 +0200 Subject: [PATCH 143/171] Add missing import back --- src/aeonlib/salt/models/request_models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/aeonlib/salt/models/request_models.py b/src/aeonlib/salt/models/request_models.py index f8e0b78..05b550f 100644 --- a/src/aeonlib/salt/models/request_models.py +++ b/src/aeonlib/salt/models/request_models.py @@ -1,5 +1,6 @@ """This module provides Pydantic models for SALT observation requests.""" +import pathlib from typing import Annotated from annotated_types import MinLen From f6a5a3b69870f83fc96e5a8c5a16f9494f315ac7 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 16 Feb 2026 18:08:56 +0200 Subject: [PATCH 144/171] Add a block submission element --- src/aeonlib/salt/models/proposal.xsd | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/aeonlib/salt/models/proposal.xsd b/src/aeonlib/salt/models/proposal.xsd index 67707c0..8580db5 100644 --- a/src/aeonlib/salt/models/proposal.xsd +++ b/src/aeonlib/salt/models/proposal.xsd @@ -4811,10 +4811,39 @@ + + + + + A block submission. + + + + + + + The year for which the submission is made. + + + + + + + The semester for which the submission is made. + + + + + + + + + + From 0c765bf9d9cac107ea9c41f09c7510d4772aba13 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 17 Feb 2026 18:03:54 +0200 Subject: [PATCH 145/171] Add a block submission element --- src/aeonlib/salt/models/request_models.py | 17 ++++++- .../serialize/templates/block_submission.xml | 8 ++++ .../models/serialize/templates/blocks.xml | 5 -- tests/salt/models/conftest.py | 4 +- tests/salt/models/serialize/test_templates.py | 23 ++++++++-- tests/salt/models/serialize/test_util.py | 46 ++++++++++--------- tests/salt/models/test_request_models.py | 7 +-- 7 files changed, 72 insertions(+), 38 deletions(-) create mode 100644 src/aeonlib/salt/models/serialize/templates/block_submission.xml delete mode 100644 src/aeonlib/salt/models/serialize/templates/blocks.xml diff --git a/src/aeonlib/salt/models/request_models.py b/src/aeonlib/salt/models/request_models.py index 05b550f..f5cd003 100644 --- a/src/aeonlib/salt/models/request_models.py +++ b/src/aeonlib/salt/models/request_models.py @@ -4,17 +4,30 @@ from typing import Annotated from annotated_types import MinLen -from pydantic import BaseModel +from pydantic import BaseModel, Field class Request(BaseModel, validate_assignment=True): """ An observation request for SALT. + A request is made up of a proposal code, which must be the proposal code of a + proposal existing already, the semester for which the request is submitted and a + non-empty list of blocks. + + The semester must be of the form yyyy-s with the year yyyy and its semester (1 or + 2). Semester 1 runs from 1 May to 1 November, semester 2 from November to May (in + the following year). For example, semester 2026-1 runs from noon on 1 May 2026 to + noon on 1 November 2026, whereas semester 2026-2 runs from noon on 1 November 2026 + to noon on 1 May 2027. + Parameters ---------- proposal_code Unique identifier of the proposal for which this request is submitted. + semester + Semester for which the submission is intended, in the form yyyy-s, where yyyy + denotes the year and s can be 1 or 2. Examples are 2025-1 or 2026-2. blocks List of blocks to observe. @@ -22,6 +35,8 @@ class Request(BaseModel, validate_assignment=True): proposal_code: str + semester: str = Field(pattern=r"\d{4}-[12]") + blocks: Annotated[list, MinLen(1)] def attachments(self) -> set[pathlib.Path]: diff --git a/src/aeonlib/salt/models/serialize/templates/block_submission.xml b/src/aeonlib/salt/models/serialize/templates/block_submission.xml new file mode 100644 index 0000000..b097948 --- /dev/null +++ b/src/aeonlib/salt/models/serialize/templates/block_submission.xml @@ -0,0 +1,8 @@ + + + {{ request.semester.split("-")[0] }} + {{ request.semester.split("-")[1] }} + {% for block in request.blocks %} + {% include "block.xml" %} + {% endfor %} + diff --git a/src/aeonlib/salt/models/serialize/templates/blocks.xml b/src/aeonlib/salt/models/serialize/templates/blocks.xml deleted file mode 100644 index 035442d..0000000 --- a/src/aeonlib/salt/models/serialize/templates/blocks.xml +++ /dev/null @@ -1,5 +0,0 @@ - - {% for block in blocks %} - {% include "block.xml" %} - {% endfor %} - diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index 40711e5..e33716d 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -36,7 +36,9 @@ @pytest.fixture() def base_request(base_block): """A simple request to edit or build from.""" - return Request(proposal_code="2025-1-SCI-042", blocks=[base_block]) + return Request( + proposal_code="2025-1-SCI-042", semester="2026-1", blocks=[base_block] + ) @pytest.fixture() diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index 330b7a8..c832e42 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -557,17 +557,32 @@ def test_position_angle( assert True -class TestBlocks: - def test_blocks(self, base_request): +class TestBlockSubmission: + def test_block_submission(self, base_request): request = base_request block = base_request.blocks[0] block_copy = deepcopy(block) request.blocks.append(block_copy) - xml = render_template("blocks.xml", blocks=request.model_dump()["blocks"]) + xml = render_template("block_submission.xml", request=request.model_dump()) - assert "" in xml + assert "" in xml assert len(re.findall(r"{year}" in xml + assert f"{semester}" in xml diff --git a/tests/salt/models/serialize/test_util.py b/tests/salt/models/serialize/test_util.py index 182e8f9..b7e9b5c 100644 --- a/tests/salt/models/serialize/test_util.py +++ b/tests/salt/models/serialize/test_util.py @@ -61,18 +61,17 @@ def test_render_template_with_escaping(): def test_replace_attachment_paths( - base_request, base_block, base_rss, base_rss_multi_object_spectroscopy + base_request, base_block, base_rss, base_rss_multi_object_spectroscopy ): """Test replacing attachment paths.""" # finder_chart deliberately includes ".." to test resolution. finder_chart = ( - pathlib.Path(__file__).parent.parent.parent - / "data/../data" - / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent.parent + / "data/../data" + / "dummy_finder_chart_1.pdf" ) mos_mask = ( - pathlib.Path( - __file__).parent.parent.parent / "data" / "dummy_rss_mos_mask.rsim" + pathlib.Path(__file__).parent.parent.parent / "data" / "dummy_rss_mos_mask.rsim" ) request = base_request block = base_block @@ -84,7 +83,7 @@ def test_replace_attachment_paths( block.instrument = rss request.blocks = [block] - xml = render_template("blocks.xml", blocks=request.model_dump()["blocks"]) + xml = render_template("block_submission.xml", request=request.model_dump()) replacements = { finder_chart.resolve(): "Included/FinderChart.pdf", @@ -100,15 +99,16 @@ def test_replace_attachment_paths( def test_missing_replacement(base_request, base_block): """Test that an error is raised if an attachment path replacement is missing.""" finder_chart = ( - pathlib.Path( - __file__).parent.parent.parent / "data" / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent.parent + / "data" + / "dummy_finder_chart_1.pdf" ) request = base_request block = base_block block.acquisition.finder_charts = [finder_chart] request.blocks = [block] - xml = render_template("blocks.xml", blocks=request.model_dump()["blocks"]) + xml = render_template("block_submission.xml", request=request.model_dump()) with pytest.raises(ValueError, match="Path missing"): replace_attachment_paths(xml, {}) @@ -120,18 +120,19 @@ def test_duplicate_replacement_key(base_request): """ # file_1a and file_1b are the same file file_1a = ( - pathlib.Path( - __file__).parent.parent.parent / "data" / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent.parent + / "data" + / "dummy_finder_chart_1.pdf" ) file_1b = ( - pathlib.Path(__file__).parent.parent.parent - / "data/../data" - / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent.parent + / "data/../data" + / "dummy_finder_chart_1.pdf" ) request = base_request - xml = render_template("blocks.xml", blocks=request.model_dump()["blocks"]) + xml = render_template("block_submission.xml", request=request.model_dump()) replacements = {file_1a: "Included/File_1a.pdf", file_1b: "Included/File_1b.pdf"} @@ -145,18 +146,19 @@ def test_duplicate_replacement_value(base_request): replacements. """ file_1 = ( - pathlib.Path( - __file__).parent.parent.parent / "data" / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent.parent + / "data" + / "dummy_finder_chart_1.pdf" ) file_2 = ( - pathlib.Path(__file__).parent.parent.parent - / "data" - / "dummy_finder_chart_2.pdf" + pathlib.Path(__file__).parent.parent.parent + / "data" + / "dummy_finder_chart_2.pdf" ) request = base_request - xml = render_template("blocks.xml", blocks=request.model_dump()["blocks"]) + xml = render_template("block_submission.xml", request=request.model_dump()) replacements = {file_1: "Included/File.pdf", file_2: "Included/File.pdf"} diff --git a/tests/salt/models/test_request_models.py b/tests/salt/models/test_request_models.py index af83342..f7e01b4 100644 --- a/tests/salt/models/test_request_models.py +++ b/tests/salt/models/test_request_models.py @@ -4,17 +4,14 @@ from pydantic import ValidationError from aeonlib.salt.models import Request +from aeonlib.salt.models.util import validate_xml, render_template class TestRequest: - def test_request(self, base_request): - """Test that a simple request can be built.""" - assert True - def test_no_blocks(self): """Test that at least one block must be supplied.""" with pytest.raises(ValidationError) as exc_info: - Request(proposal_code="2025-1-SCI-042", blocks=[]) + Request(proposal_code="2025-1-SCI-042", semester="2026-1", blocks=[]) assert exc_info.value.errors()[0]["loc"] == ("blocks",) assert exc_info.value.errors()[0]["type"] == "too_short" From 51cc98145b12c204824a104537cb60d018b311cf Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 25 Feb 2026 16:45:30 +0200 Subject: [PATCH 146/171] Remove unnecessary imports --- tests/salt/models/test_block_models.py | 2 +- tests/salt/models/test_request_models.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/salt/models/test_block_models.py b/tests/salt/models/test_block_models.py index ac495a4..a3c2d50 100644 --- a/tests/salt/models/test_block_models.py +++ b/tests/salt/models/test_block_models.py @@ -5,7 +5,7 @@ import pytest from pydantic import ValidationError -from aeonlib.salt.models import Block, Acquisition +from aeonlib.salt.models import Acquisition from aeonlib.salt.models.block_models import ReferenceStar from aeonlib.salt.models.util import render_template, validate_xml diff --git a/tests/salt/models/test_request_models.py b/tests/salt/models/test_request_models.py index f7e01b4..c9cf196 100644 --- a/tests/salt/models/test_request_models.py +++ b/tests/salt/models/test_request_models.py @@ -4,7 +4,6 @@ from pydantic import ValidationError from aeonlib.salt.models import Request -from aeonlib.salt.models.util import validate_xml, render_template class TestRequest: From 9ba61412ecd7046c8b98532b8f96af772cd45520 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 25 Feb 2026 16:53:12 +0200 Subject: [PATCH 147/171] Improve testing with duplicate attachments --- tests/salt/models/test_request_models.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/salt/models/test_request_models.py b/tests/salt/models/test_request_models.py index c9cf196..636eb53 100644 --- a/tests/salt/models/test_request_models.py +++ b/tests/salt/models/test_request_models.py @@ -1,4 +1,5 @@ import pathlib +from copy import deepcopy import pytest from pydantic import ValidationError @@ -85,14 +86,23 @@ def test_duplicate_attachments( rss = base_rss configuration = base_rss_multi_object_spectroscopy configuration.mask = mos_mask - block.acquisition.finder_charts = [ + rss.configuration = configuration + block.instrument = rss + + block1 = block + block2 = deepcopy(block1) + + block1.acquisition.finder_charts = [ finder_chart_1a, finder_chart_2, finder_chart_1b, ] - rss.configuration = configuration - block.instrument = rss - request.blocks = [block] + block2.acquisition.finder_charts = [ + finder_chart_1c, + finder_chart_2, + ] + + request.blocks = [block1, block2] assert request.attachments() == { finder_chart_1a.resolve(), From 068d436ed78bf47ba22b27bbbfc1118781e13535 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Wed, 25 Feb 2026 18:01:17 +0200 Subject: [PATCH 148/171] Add a method for mapping attachments to paths for the submitted XML --- src/aeonlib/salt/models/util.py | 29 ++++++++++++++++++++++++ tests/salt/models/serialize/test_util.py | 24 ++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index 8f72404..71c9fd0 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -4,6 +4,7 @@ import uuid import zoneinfo from typing import Any +from uuid import uuid4 import astropy.units as u from astropy.coordinates import Angle @@ -235,6 +236,34 @@ def _upper(s: str | None) -> str | None: """ +def attachment_path_replacements( + attachments: list[pathlib.Path], +) -> dict[pathlib.Path, str]: + """ + Return a dictionary of attachments and corresponding paths to use in submitted XML. + + The paths to use for submitted XML are of the form "Included/", + where denotes a UUID version 4 string and is the file extension + of the attachment, including the dot and in lower case, such as ".jpg" or ".pdf". + + Parameters + ---------- + attachments + List of attachment paths. + + Returns + ------- + Dictionary of attachment paths and paths for submitted XML. + """ + replacements = {} + for attachment in attachments: + extension = attachment.suffix.lower() + replacement = f"Included/{uuid4()}{extension}" + replacements[attachment] = replacement + + return replacements + + def replace_attachment_paths(xml: str, replacements: dict[pathlib.Path, str]) -> str: """ Replace the attachment paths in the given XML. diff --git a/tests/salt/models/serialize/test_util.py b/tests/salt/models/serialize/test_util.py index b7e9b5c..39d1abc 100644 --- a/tests/salt/models/serialize/test_util.py +++ b/tests/salt/models/serialize/test_util.py @@ -5,6 +5,7 @@ from jinja2 import FileSystemLoader from aeonlib.salt.models.util import ( + attachment_path_replacements, validate_xml, render_template, replace_attachment_paths, @@ -60,6 +61,29 @@ def test_render_template_with_escaping(): assert "a >= 1 & a <= 5" in rendered +def test_attachment_replacement_paths(): + """Test that the replacement paths for attachments are generated correctly.""" + path1 = pathlib.Path("/a/b/c.PNG") + path2 = pathlib.Path("a.pdf") + path3 = pathlib.Path("something") + attachments = [path1, path2, path3] + replacements = attachment_path_replacements(attachments) + + assert len(replacements) == 3 + + for path in [path1, path2, path3]: + assert replacements[path].startswith("Included/") + + # The replacement looks as if it includes a UUID v4 string. + assert len(replacements[path1]) > 36 + assert "-" in replacements[path1] + + # The correct file extension (if any) is used. + assert replacements[path1].endswith(".png") + assert replacements[path2].endswith(".pdf") + assert not replacements[path3].endswith(".") + + def test_replace_attachment_paths( base_request, base_block, base_rss, base_rss_multi_object_spectroscopy ): From 5a28564b2a1ff17caf8455fed70fcad0d723c483 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 26 Feb 2026 12:13:21 +0200 Subject: [PATCH 149/171] Add a method for mapping attachments to paths for the submitted XML --- src/aeonlib/salt/models/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index 71c9fd0..c2cbeaf 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -3,7 +3,7 @@ import pathlib import uuid import zoneinfo -from typing import Any +from typing import Any, Iterable from uuid import uuid4 import astropy.units as u @@ -237,7 +237,7 @@ def _upper(s: str | None) -> str | None: def attachment_path_replacements( - attachments: list[pathlib.Path], + attachments: Iterable[pathlib.Path], ) -> dict[pathlib.Path, str]: """ Return a dictionary of attachments and corresponding paths to use in submitted XML. From 25a2df9676f6e9de529a96a45cea6d1d19ac2d5e Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 5 Mar 2026 13:51:47 +0200 Subject: [PATCH 150/171] Fix the name of the dummy slit mask file --- .../data/{dummy_rss_mos_mask.rsim => dummy_rss_mos_mask.rsmt} | 0 tests/salt/models/conftest.py | 2 +- tests/salt/models/serialize/test_util.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename tests/salt/data/{dummy_rss_mos_mask.rsim => dummy_rss_mos_mask.rsmt} (100%) diff --git a/tests/salt/data/dummy_rss_mos_mask.rsim b/tests/salt/data/dummy_rss_mos_mask.rsmt similarity index 100% rename from tests/salt/data/dummy_rss_mos_mask.rsim rename to tests/salt/data/dummy_rss_mos_mask.rsmt diff --git a/tests/salt/models/conftest.py b/tests/salt/models/conftest.py index e33716d..00c61ad 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/models/conftest.py @@ -173,7 +173,7 @@ def base_rss_longslit_spectroscopy(base_rss_spectroscopy): def base_rss_multi_object_spectroscopy(base_rss_spectroscopy): return RssMultiObjectSpectroscopy( **base_rss_spectroscopy.model_dump(), - mask=pathlib.Path(__file__).parent.parent / "data" / "dummy_rss_mos_mask.rsim", + mask=pathlib.Path(__file__).parent.parent / "data" / "dummy_rss_mos_mask.rsmt", ) diff --git a/tests/salt/models/serialize/test_util.py b/tests/salt/models/serialize/test_util.py index 39d1abc..3f8c0be 100644 --- a/tests/salt/models/serialize/test_util.py +++ b/tests/salt/models/serialize/test_util.py @@ -95,7 +95,7 @@ def test_replace_attachment_paths( / "dummy_finder_chart_1.pdf" ) mos_mask = ( - pathlib.Path(__file__).parent.parent.parent / "data" / "dummy_rss_mos_mask.rsim" + pathlib.Path(__file__).parent.parent.parent / "data" / "dummy_rss_mos_mask.rsmt" ) request = base_request block = base_block From d105a6bd698ba1e1a0096a25ef5ec313db3503be Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 5 Mar 2026 13:52:18 +0200 Subject: [PATCH 151/171] Add zip file export --- src/aeonlib/salt/models/request_models.py | 32 ++++++- tests/salt/data/dummy_finder_chart_3.png | Bin 0 -> 14857 bytes tests/salt/models/test_request_models.py | 100 +++++++++++++++++++--- 3 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 tests/salt/data/dummy_finder_chart_3.png diff --git a/src/aeonlib/salt/models/request_models.py b/src/aeonlib/salt/models/request_models.py index f5cd003..fc6281c 100644 --- a/src/aeonlib/salt/models/request_models.py +++ b/src/aeonlib/salt/models/request_models.py @@ -1,11 +1,19 @@ """This module provides Pydantic models for SALT observation requests.""" +import os import pathlib -from typing import Annotated +import zipfile +from typing import Annotated, BinaryIO from annotated_types import MinLen from pydantic import BaseModel, Field +from aeonlib.salt.models.util import ( + render_template, + attachment_path_replacements, + replace_attachment_paths, +) + class Request(BaseModel, validate_assignment=True): """ @@ -67,3 +75,25 @@ def attachments(self) -> set[pathlib.Path]: # Remove duplicates return set(a.resolve() for a in _attachments) + + def export(self, out: pathlib.Path | os.PathLike | str | BinaryIO) -> None: + """ + Export this request as a zip file. + + Parameters + ---------- + out + Path of the zip file or a byte stream. + """ + with zipfile.ZipFile(out, "w") as archive: + block_submission_xml = render_template( + "block_submission.xml", request=self.model_dump() + ) + attachments = self.attachments() + replacements = attachment_path_replacements(attachments) + block_submission_xml = replace_attachment_paths( + block_submission_xml, replacements + ) + archive.writestr("BlockSubmission.xml", block_submission_xml) + for path, zip_path in replacements.items(): + archive.write(path, zip_path) diff --git a/tests/salt/data/dummy_finder_chart_3.png b/tests/salt/data/dummy_finder_chart_3.png new file mode 100644 index 0000000000000000000000000000000000000000..e490487e9a8e211149714e58251a043f51e57c0a GIT binary patch literal 14857 zcmeHO`Cn4${>P=Xrox&jceH7y<+hkPZr6p%DW}P_(I%}7G&9EBa!GIjw5F^H^foOn zspNFizE=~=DF_v8nxHNU6)6@9>QPBi2muigxQEI4BfhWK`Qf}?&vTxK^EuCFd4E3d z&v|kAU}X66r7lZhFxc{a-+dbmgIUMGU^e^g7lU^;7Cxzm!K^l+jyZu>LZ^)BgMD&EN0*J%_L>`G->ldoQ0` zxBcJItAka)YI46IEhBY1Iqe)?@q;nY!L5#T0^@MPzS!Z`x~E-3tm`EuWANSG|E?%W zELmY=V?OkAW%XK{^TnYy&zONP@9s%HnB-JNa(1#;TDiu2f4IM0%KaN%=a$+LLc98hOB>$$V|uN`Tg2@&bV(t``D#ol8f3o8!&JG(01!}#@oJqFyJ^xwiyzj5$v zEh+YXWK|co1QcMo$G)!pTV8^VRnlpj;L=qK5=fgA3vW`~wb+{db}??n*9#JyV#GEJ z4=-76OZva%AKkuRkg&a>!_M;dmZ__~Q;h3S;PnLw2@(15g%w*rhwTl1T{#;UB!CA) zKnH^?w%`tbyP`>9Ytm4O2h8(Qq!}CKF#CdNl%b!kx*TS4ufo*Brr9B1igR~i3#K$nKc+R9xZu~nhtpAUzPR|gFjF?Ql=gLw%Ii1D*kwJB3K&8 zSNC&5^B8R3mIqQd8Am*EmTH#&JYTTAu`^VR+e$QuBrE6!A#?W8#5u=~pjavle!VPRq@1fD7yg(+!8IGQA3xUV$3Ejs}WE=RlhDLF^#=MBSTGM1P#hqe6 zocXu{i39b83;~?^m36vqOe8;_m(##KF3%1cNY#2ws~K39w%$CR5Lp_kH!GBH8$-`> zyfr*5Glv0c(H}pRzTWWAaBTqlR2Cor(_XRNwWgWxu;x8mg`r%<0EQHz0MnSFgmMWSET#WUXO9ijpIF zo@p#H+*~MD)d_A;i^e88z+>c63+cBcPT3$3-EOp0hWE)w! zp3F$$6W-aIr3jB!pt~EiHFNBI-br0AmEb!#y57GEG22?Y70LSVk1ad!IxZR|H_NfC z!hE?mEWB?TeQjFp280zBU7`SP(#pr7eOJ))BNOcsqg?$IPB_DObED;(TH_Q3os9=v zC2@7BGFBNM&q;N`Jmlp)Zri#pB`&QqWMpM|Tv6Tp=mszMj?u5Zs@U(ID&HeM7TZP8 zp7f;OY^E`K?>8V#%5rXfd#Ais?{1eiJtCl()P0QmcZi&0faUVJUUW@?Vc~`tVX^%%$gtbibWeIV`EkHIK0^) zk!ZDmKR{QiqVSV3O&-P%;Z6Bp+(*n0*?DQJIK7i^Dwyg?cLk1f8nMhSSTU~=okkpM z+JAg9x=8^#6IVszRI@VT3B=N%fyDe~-T4Av)`i1S1#hn19Kd$Uj>lr#MT3V`u|G5k zYR125(%BR|F`q}MZ;ufGsZ12SZMLv%5Rp1SL>z3&k82(uZYuzTms!m!I7;SpfD+mX zbUX_|F`&kGR2=ESq#RZ`9Ca%T9IwQe`w)D(OGOVjh<3eEL1b0ii6`km*|ARfS?aN# zJ9)nFvfkh`Yq+{C;$3KMq_K=6dH)=DTqeSKSi%-vmrC!!Ts}-=8K-001IdQ+h-WD$ zYdLRz1tZ=xJNe;GIcjiZ4J{5o_sj#;WAoF*hzd00>D*SP5scipkojRan!F;sMQPB< zx_akyA8?^}7>vG0EsqNR_`I%FKb=WzFL*DgdtA+5(*oCSw&3B<_HM5+4Pw(QEvhkS zaH2tXzA(@=yTPY6MgQl9&<8aa0##M~j+zTcGbQ=95k%128}d&cP?Z|gRs5n;j8_-7 z{ce)sSz@CcOKBl!nxa4~)m5Yt`h+NQ5UFh|t#dlPqHT+af{kzVwy|`qzSh!X1cA1WqttA3`PpslR32Oes2$_wd>@ zT7jFo&wn6F+8Q+@A@+ET3veb57z5m@t66;+G!aT3#G5Cz<#-joZvAjA@A)43V8a`20g6-t{7|MGzcW;j;_aEVQ6H7p*uM z%Im{}7)KeeZ>vdk!K@ZiF*Pq;FoB#{9MC*YC!@mcWFq?G)S7x%YEFlInwGUDyTgOy z{8D*ViktGnPwW>2kI*E&6iY=k^wk&rX2I-N%d(?`HI~Rt#*viXrYiojh*YW%!@^D z?;swz5hr6P16c)0u7w=-?Z8{#>g-Y@ibj__J3V_F?|tlU``)(IjTYB8_qW;#Q2 zKeJOq)f$ZT@}R|R`YZj9)Pmj}U$SHKIMTd}J~fGwLG26!KHeceA+h1HoRjiHeI6;o zfurr-*V*YC8kHJgZfWMSIvr_*Nma3poD0UJwDfFO9VdMmR~hOA=yZIdX@=Q9n&rOV zl1@uwhb}qCsH(bB3`n5)>0Fn~y2NaY zCu3Pyy)a<28cf@H5|a>9peu#cY4OaXx`s=r2{q$+kTXl{+;9B!K=nZPdlQNK8kh(L@&C z8{6`ZE}ytb>ln)p5$H8(#Q9orOnZo2F9jIwS%*_e_ml`HA)nlqj0uq)X^bMwT;slb ztHB%ps%baWcD>#(7(>DFgF|1ig(m=tCY{u%bGbediDd|8R&(b*S`r7XCeg|9gr(|0 z?S1VO3HYJS4~tsMJ}coIp(w5V}_6wf^-&hAPgbKK4G>faoXGncwJ?ke{Q0G`l@J^m{Y zn3H{cZ}z*scp2xY)QyM;%c&u|Qn93V__k3I4g9%W~ST2~$aL%^C zW}fTdhrK}|Qv{@Flk6!rvX3SP}X9j*iX-GvI-gC{?iOjkTU3HE@qEJhfZa~u?y6XRoHIHt0 zMQh1aH;%BJXi`xe`L$&l9}8azP}-Kc2s3JNQ5Cuf)gl%*b^{PLd61@j#1AmE3St9P zi{5h`T+Dmk&)s$vK69ITTp@Mm`^sq|GyJpxC~x}?FacA~hZ$t514k1C_?E-f=LcuA zC0S%4E(9=3G@wZ`T!fA3?wjoj=Lb!4&`Z3)Q(}wU1N9<8PhX#*nTV{zlDfV9IH^ul z4E6YZk0dWvnN`s}tpJ7K;QwM^A3t-7DSe$A(!dsmWqcw$U02s8M7Xo-IOe3EY6jm= zH1{SJxxn{)Vn4P^oMLt*U)ud?re5OaZT3Z@eOG<{iQ6o#2~-n7y%MNb0`*FuUJ1kvA#Mn9Lx>wf+z{f1 k5I2Ol;r|D2XtwMBZPSgPufz9R{%p8!Pvo~PyA#g+4-b(DvH$=8 literal 0 HcmV?d00001 diff --git a/tests/salt/models/test_request_models.py b/tests/salt/models/test_request_models.py index 636eb53..009f533 100644 --- a/tests/salt/models/test_request_models.py +++ b/tests/salt/models/test_request_models.py @@ -1,4 +1,7 @@ +import io +import os.path import pathlib +import zipfile from copy import deepcopy import pytest @@ -16,7 +19,7 @@ def test_no_blocks(self): assert exc_info.value.errors()[0]["type"] == "too_short" def test_no_attachments( - self, base_request, base_block, base_rss, base_rss_longslit_spectroscopy + self, base_request, base_block, base_rss, base_rss_longslit_spectroscopy ): """Test the case that the request includes no attachments.""" request = base_request @@ -31,17 +34,20 @@ def test_no_attachments( assert request.attachments() == set() def test_multiple_attachments( - self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy + self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy ): """Test the case that the request includes multiple attachments.""" finder_chart_1 = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" + pathlib.Path( + __file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" ) finder_chart_2 = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" + pathlib.Path( + __file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" ) mos_mask = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_rss_mos_mask.rsim" + pathlib.Path( + __file__).parent.parent / "data" / "dummy_rss_mos_mask.rsmt" ) request = base_request block = base_block @@ -60,25 +66,27 @@ def test_multiple_attachments( } def test_duplicate_attachments( - self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy + self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy ): """Test the case that the request uses the same attachment multiple times.""" # finder_chart_1a, finder_chart_1b and finder_chart_1c denote the sane file finder_chart_1a = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" + pathlib.Path( + __file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" ) finder_chart_1b = ( - pathlib.Path(__file__).parent.parent - / "data/../data" - / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent + / "data/../data" + / "dummy_finder_chart_1.pdf" ) finder_chart_1c = ( - pathlib.Path(__file__).parent.parent - / "data/../../salt/data" - / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent + / "data/../../salt/data" + / "dummy_finder_chart_1.pdf" ) finder_chart_2 = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" + pathlib.Path( + __file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" ) mos_mask = finder_chart_1c # as we are testing for duplicates request = base_request @@ -108,3 +116,67 @@ def test_duplicate_attachments( finder_chart_1a.resolve(), finder_chart_2.resolve(), } + + def test_export(self, base_block, base_rss, base_rss_multi_object_spectroscopy): + """Test that a correct zip file is generated.""" + + # Set up the attachments. + finder_chart_1 = ( + pathlib.Path( + __file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" + ) + finder_chart_2 = ( + pathlib.Path( + __file__).parent.parent / "data" / "dummy_finder_chart_3.png" + ) + mos_file = ( + pathlib.Path( + __file__).parent.parent / "data" / "dummy_rss_mos_mask.rsmt" + ) + + # Store the attachment sizes. + finder_chart_1_size = os.path.getsize(finder_chart_1) + finder_chart_2_size = os.path.getsize(finder_chart_2) + mos_file_size = os.path.getsize(mos_file) + + # Set up the first block. + block1 = deepcopy(base_block) + block1.acquisition.finder_charts = [finder_chart_1] + base_rss_multi_object_spectroscopy.mask = mos_file + base_rss.configuration = base_rss_multi_object_spectroscopy + block1.instrument = base_rss + + # Set up the second block. + block2 = deepcopy(base_block) + block2.acquisition.finder_charts = [finder_chart_2] + + # Generate the zip file. + request = Request( + proposal_code="2026-1-SCI-042", semester="2026-1", blocks=[block1, block2] + ) + zip_content = io.BytesIO() + request.export(zip_content) + + # Check the content of the generated zip file. + zip_content.seek(0) + with zipfile.ZipFile(zip_content) as archive: + block_submission = archive.read("BlockSubmission.xml").decode( + encoding="utf-8" + ) + for file in archive.namelist(): + if file != "BlockSubmission.xml": + assert file.startswith("Included/") + assert ( + file.endswith(".pdf") + or file.endswith(".png") + or file.endswith(".rsmt") + ) + assert file in block_submission + + content = archive.read(file) + if file.endswith(".pdf"): + assert len(content) == finder_chart_1_size + elif file.endswith(".png"): + assert len(content) == finder_chart_2_size + elif file.endswith(".rsmt"): + assert len(content) == mos_file_size From 07b212f5d2c9ff6acbddbceadb7dde78fd32e3a9 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 19 Apr 2026 18:29:21 +0200 Subject: [PATCH 152/171] Serialize observation windows --- pyproject.toml | 5 +- .../salt/models/serialize/templates/block.xml | 6 ++ src/aeonlib/salt/models/util.py | 17 ++++- tests/salt/models/serialize/test_templates.py | 55 +++++++++++++++ uv.lock | 69 ++++++++++++++++++- 5 files changed, 148 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e0f0a4f..1fba656 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,10 @@ codegen = [ "textcase>=0.2.1", ] -dev = ["pytest>=8.3.5"] +dev = [ + "pytest>=8.3.5", + "time-machine>=3.2.0", +] [project.optional-dependencies] eso = ["p2api>=1.0.10"] diff --git a/src/aeonlib/salt/models/serialize/templates/block.xml b/src/aeonlib/salt/models/serialize/templates/block.xml index 654b8fd..ff52e94 100644 --- a/src/aeonlib/salt/models/serialize/templates/block.xml +++ b/src/aeonlib/salt/models/serialize/templates/block.xml @@ -95,6 +95,12 @@ {% endwith %} + {% for window in block.windows %} + + {{ window.start | utc }} + {{ window.end | utc }} + + {% endfor %} Normal HTML diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index c2cbeaf..1cd1315 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -3,8 +3,9 @@ import pathlib import uuid import zoneinfo -from typing import Any, Iterable +from typing import Any, Iterable, cast from uuid import uuid4 +from zoneinfo import ZoneInfo import astropy.units as u from astropy.coordinates import Angle @@ -94,7 +95,7 @@ def validate_xml(xml: str) -> None: try: xml_doc = etree.parse(io.BytesIO(xml.encode("utf-8"))) - _schema.assertValid(xml_doc) + cast(etree.XMLSchema, _schema).assertValid(xml_doc) # noqa except (etree.DocumentInvalid, etree.XMLSyntaxError) as e: raise ValueError(str(e)) @@ -129,6 +130,17 @@ def _year_as_iso_timestamp(year): return t.isoformat() +def _to_utc(t: datetime.datetime | None) -> str: + if t is None: + t = datetime.datetime.now(ZoneInfo("UTC")) + else: + if t.tzinfo: + raise ValueError("The datetime instance must be naive.") + t = t.replace(tzinfo=ZoneInfo("UTC")) + + return cast(datetime.datetime, t).strftime("%Y-%m-%dT%H:%M:%SZ") + + def _sign(value): return "+" if value >= 0 else "-" @@ -178,6 +190,7 @@ def render_template( env.filters["iodine_cell_position"] = _iodine_cell_position env.filters["nirwals_articulation_station"] = _nirwals_articulation_station env.filters["year_as_iso_timestamp"] = _year_as_iso_timestamp + env.filters["utc"] = _to_utc env.filters["sign"] = _sign env.globals["uuid"] = _uuid env.globals["to_angle"] = _to_angle diff --git a/tests/salt/models/serialize/test_templates.py b/tests/salt/models/serialize/test_templates.py index c832e42..d9ebbdb 100644 --- a/tests/salt/models/serialize/test_templates.py +++ b/tests/salt/models/serialize/test_templates.py @@ -1,12 +1,16 @@ import re from copy import deepcopy +from datetime import datetime from typing import Any +from zoneinfo import ZoneInfo import astropy.units as u import pytest +import time_machine from astropy.coordinates import Angle from astropy.units import Quantity +from aeonlib.models import Window from aeonlib.salt.models import SalticamFilterSequenceStep from aeonlib.salt.models.util import render_template, validate_xml @@ -556,6 +560,57 @@ def test_position_angle( validate_xml(xml) assert True + def test_windows_with_start_and_end(self, base_block): + """Test that windows with start and end are serialized correctly.""" + block = base_block + block.windows = [ + Window( + start=datetime( + 2026, 4, 20, 3, 4, 5, 0, tzinfo=ZoneInfo("Africa/Johannesburg") + ), + end=datetime( + 2026, 4, 22, 4, 5, 6, 0, tzinfo=ZoneInfo("Africa/Johannesburg") + ), + ), + Window( + start=datetime( + 2026, 5, 1, 22, 0, 17, 0, tzinfo=ZoneInfo("Africa/Johannesburg") + ), + end=datetime( + 2026, 5, 2, 2, 15, 6, 0, tzinfo=ZoneInfo("Africa/Johannesburg") + ), + ), + ] + + xml = render_template("block.xml", block=block.model_dump()) + + assert "2026-04-20T01:04:05Z" in xml + assert "2026-04-22T02:05:06Z" in xml + assert "2026-05-01T20:00:17Z" in xml + assert "2026-05-02T00:15:06Z" in xml + + validate_xml(xml) + assert True + + def test_window_with_end_only(self, base_block): + """Test that a window with an end only is serialized correctly.""" + block = base_block + block.windows = [ + Window( + end=datetime( + 2026, 5, 21, 2, 21, 7, 0, tzinfo=ZoneInfo("Africa/Johannesburg") + ) + ) + ] + + with time_machine.travel( + datetime(2026, 5, 21, 17, 0, 0, 0, tzinfo=ZoneInfo("UTC")) + ): + xml = render_template("block.xml", block=block.model_dump()) + + assert "2026-05-21T17:00:00Z" in xml + assert "2026-05-21T00:21:07Z" in xml + class TestBlockSubmission: def test_block_submission(self, base_request): diff --git a/uv.lock b/uv.lock index c7f6329..7365e1c 100644 --- a/uv.lock +++ b/uv.lock @@ -35,6 +35,7 @@ codegen = [ ] dev = [ { name = "pytest" }, + { name = "time-machine" }, ] [package.metadata] @@ -58,7 +59,10 @@ codegen = [ { name = "jinja2", specifier = ">=3.1.6" }, { name = "textcase", specifier = ">=0.2.1" }, ] -dev = [{ name = "pytest", specifier = ">=8.3.5" }] +dev = [ + { name = "pytest", specifier = ">=8.3.5" }, + { name = "time-machine", specifier = ">=3.2.0" }, +] [[package]] name = "annotated-types" @@ -844,6 +848,69 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/a3/7e8d624362ca16a51df435d14c08e07adf266c27d16fad422f954a3abbbd/textcase-0.4.3-py3-none-any.whl", hash = "sha256:2f1a1c069ba60e224455ae6d45ca35344ad6a2ffffd05d6bd8161c0c34515f30", size = 6474, upload-time = "2025-04-18T16:13:20.621Z" }, ] +[[package]] +name = "time-machine" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/fc/37b02f6094dbb1f851145330460532176ed2f1dc70511a35828166c41e52/time_machine-3.2.0.tar.gz", hash = "sha256:a4ddd1cea17b8950e462d1805a42b20c81eb9aafc8f66b392dd5ce997e037d79", size = 14804, upload-time = "2025-12-17T23:33:02.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/8b/080c8eedcd67921a52ba5bd0e075362062509ab63c86fc1a0442fad241a6/time_machine-3.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cc4bee5b0214d7dc4ebc91f4a4c600f1a598e9b5606ac751f42cb6f6740b1dbb", size = 19255, upload-time = "2025-12-17T23:31:58.057Z" }, + { url = "https://files.pythonhosted.org/packages/66/17/0e5291e9eb705bf8a5a1305f826e979af307bbeb79def4ddbf4b3f9a81e0/time_machine-3.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ca036304b4460ae2fdc1b52dd8b1fa7cf1464daa427fc49567413c09aa839c1", size = 15360, upload-time = "2025-12-17T23:31:59.048Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/9ab87b71d2e2b62463b9b058b7ae7ac09fb57f8fcd88729dec169d304340/time_machine-3.2.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5442735b41d7a2abc2f04579b4ca6047ed4698a8338a4fec92c7c9423e7938cb", size = 33029, upload-time = "2025-12-17T23:32:00.413Z" }, + { url = "https://files.pythonhosted.org/packages/4b/26/b5ca19da6f25ea905b3e10a0ea95d697c1aeba0404803a43c68f1af253e6/time_machine-3.2.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:97da3e971e505cb637079fb07ab0bcd36e33279f8ecac888ff131f45ef1e4d8d", size = 34579, upload-time = "2025-12-17T23:32:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/79/ca/6ac7ad5f10ea18cc1d9de49716ba38c32132c7b64532430d92ef240c116b/time_machine-3.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3cdda6dee4966e38aeb487309bb414c6cb23a81fc500291c77a8fcd3098832e7", size = 35961, upload-time = "2025-12-17T23:32:02.521Z" }, + { url = "https://files.pythonhosted.org/packages/33/67/390dd958bed395ab32d79a9fe61fe111825c0dd4ded54dbba7e867f171e6/time_machine-3.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:33d9efd302a6998bcc8baa4d84f259f8a4081105bd3d7f7af7f1d0abd3b1c8aa", size = 34668, upload-time = "2025-12-17T23:32:03.585Z" }, + { url = "https://files.pythonhosted.org/packages/da/57/c88fff034a4e9538b3ae7c68c9cfb283670b14d17522c5a8bc17d29f9a4b/time_machine-3.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3a0b0a33971f14145853c9bd95a6ab0353cf7e0019fa2a7aa1ae9fddfe8eab50", size = 32891, upload-time = "2025-12-17T23:32:04.656Z" }, + { url = "https://files.pythonhosted.org/packages/2d/70/ebbb76022dba0fec8f9156540fc647e4beae1680c787c01b1b6200e56d70/time_machine-3.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2d0be9e5f22c38082d247a2cdcd8a936504e9db60b7b3606855fb39f299e9548", size = 34080, upload-time = "2025-12-17T23:32:06.146Z" }, + { url = "https://files.pythonhosted.org/packages/db/9a/2ca9e7af3df540dc1c79e3de588adeddb7dcc2107829248e6969c4f14167/time_machine-3.2.0-cp312-cp312-win32.whl", hash = "sha256:3f74623648b936fdce5f911caf386c0a0b579456410975de8c0dfeaaffece1d8", size = 17371, upload-time = "2025-12-17T23:32:07.164Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ce/21d23efc9c2151939af1b7ee4e60d86d661b74ef32b8eaa148f6fe8c899c/time_machine-3.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:34e26a41d994b5e4b205136a90e9578470386749cc9a2ecf51ca18f83ce25e23", size = 18132, upload-time = "2025-12-17T23:32:08.447Z" }, + { url = "https://files.pythonhosted.org/packages/2f/34/c2b70be483accf6db9e5d6c3139bce3c38fe51f898ccf64e8d3fe14fbf4d/time_machine-3.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:0615d3d82c418d6293f271c348945c5091a71f37e37173653d5c26d0e74b13a8", size = 16930, upload-time = "2025-12-17T23:32:09.477Z" }, + { url = "https://files.pythonhosted.org/packages/ee/cd/43ad5efc88298af3c59b66769cea7f055567a85071579ed40536188530c1/time_machine-3.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c421a8eb85a4418a7675a41bf8660224318c46cc62e4751c8f1ceca752059090", size = 19318, upload-time = "2025-12-17T23:32:10.518Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f6/084010ef7f4a3f38b5a4900923d7c85b29e797655c4f6ee4ce54d903cca8/time_machine-3.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f4e758f7727d0058c4950c66b58200c187072122d6f7a98b610530a4233ea7b", size = 15390, upload-time = "2025-12-17T23:32:11.625Z" }, + { url = "https://files.pythonhosted.org/packages/25/aa/1cabb74134f492270dc6860cb7865859bf40ecf828be65972827646e91ad/time_machine-3.2.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:154bd3f75c81f70218b2585cc12b60762fb2665c507eec5ec5037d8756d9b4e0", size = 33115, upload-time = "2025-12-17T23:32:13.219Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/78c5d7dfa366924eb4dbfcc3fc917c39a4280ca234b12819cc1f16c03d88/time_machine-3.2.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d50cfe5ebea422c896ad8d278af9648412b7533b8ea6adeeee698a3fd9b1d3b7", size = 34705, upload-time = "2025-12-17T23:32:14.29Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/d5e877c24541f674c6869ff6e9c56833369796010190252e92c9d7ae5f0f/time_machine-3.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:636576501724bd6a9124e69d86e5aef263479e89ef739c5db361469f0463a0a1", size = 36104, upload-time = "2025-12-17T23:32:15.354Z" }, + { url = "https://files.pythonhosted.org/packages/22/1c/d4bae72f388f67efc9609f89b012e434bb19d9549c7a7b47d6c7d9e5c55d/time_machine-3.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40e6f40c57197fcf7ec32d2c563f4df0a82c42cdcc3cab27f688e98f6060df10", size = 34765, upload-time = "2025-12-17T23:32:16.434Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c3/ac378cf301d527d8dfad2f0db6bad0dfb1ab73212eaa56d6b96ee5d9d20b/time_machine-3.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a1bcf0b846bbfc19a79bc19e3fa04d8c7b1e8101c1b70340ffdb689cd801ea53", size = 33010, upload-time = "2025-12-17T23:32:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/06/35/7ce897319accda7a6970b288a9a8c52d25227342a7508505a2b3d235b649/time_machine-3.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae55a56c179f4fe7a62575ad5148b6ed82f6c7e5cf2f9a9ec65f2f5b067db5f5", size = 34185, upload-time = "2025-12-17T23:32:18.566Z" }, + { url = "https://files.pythonhosted.org/packages/bf/28/f922022269749cb02eee2b62919671153c4088994fa955a6b0e50327ff81/time_machine-3.2.0-cp313-cp313-win32.whl", hash = "sha256:a66fe55a107e46916007a391d4030479df8864ec6ad6f6a6528221befc5c886e", size = 17397, upload-time = "2025-12-17T23:32:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/fd87cde397f4a7bea493152f0aca8fd569ec709cad9e0f2ca7011eb8c7f7/time_machine-3.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:30c9ce57165df913e4f74e285a8ab829ff9b7aa3e5ec0973f88f642b9a7b3d15", size = 18139, upload-time = "2025-12-17T23:32:20.991Z" }, + { url = "https://files.pythonhosted.org/packages/75/81/b8ce58233addc5d7d54d2fabc49dcbc02d79e3f079d150aa1bec3d5275ef/time_machine-3.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:89cad7e179e9bdcc84dcf09efe52af232c4cc7a01b3de868356bbd59d95bd9b8", size = 16964, upload-time = "2025-12-17T23:32:22.075Z" }, + { url = "https://files.pythonhosted.org/packages/67/e7/487f0ba5fe6c58186a5e1af2a118dfa2c160fedb37ef53a7e972d410408e/time_machine-3.2.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:59d71545e62525a4b85b6de9ab5c02ee3c61110fd7f636139914a2335dcbfc9c", size = 20000, upload-time = "2025-12-17T23:32:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/e1/17/eb2c0054c8d44dd42df84ccd434539249a9c7d0b8eb53f799be2102500ab/time_machine-3.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:999672c621c35362bc28e03ca0c7df21500195540773c25993421fd8d6cc5003", size = 15657, upload-time = "2025-12-17T23:32:24.125Z" }, + { url = "https://files.pythonhosted.org/packages/43/21/93443b5d1dd850f8bb9442e90d817a9033dcce6bfbdd3aabbb9786251c80/time_machine-3.2.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5faf7397f0580c7b9d67288522c8d7863e85f0cffadc0f1fccdb2c3dfce5783e", size = 39216, upload-time = "2025-12-17T23:32:25.542Z" }, + { url = "https://files.pythonhosted.org/packages/9f/9e/18544cf8acc72bb1dc03762231c82ecc259733f4bb6770a7bbe5cd138603/time_machine-3.2.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3dd886ec49f1fa5a00e844f5947e5c0f98ce574750c24b7424c6f77fc1c3e87", size = 40764, upload-time = "2025-12-17T23:32:26.643Z" }, + { url = "https://files.pythonhosted.org/packages/27/f7/9fe9ce2795636a3a7467307af6bdf38bb613ddb701a8a5cd50ec713beb5e/time_machine-3.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da0ecd96bc7bbe450acaaabe569d84e81688f1be8ad58d1470e42371d145fb53", size = 43526, upload-time = "2025-12-17T23:32:27.693Z" }, + { url = "https://files.pythonhosted.org/packages/03/c1/a93e975ba9dec22e87ec92d18c28e67d36bd536f9119ffa439b2892b0c9c/time_machine-3.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:158220e946c1c4fb8265773a0282c88c35a7e3bb5d78e3561214e3b3231166f3", size = 41727, upload-time = "2025-12-17T23:32:28.985Z" }, + { url = "https://files.pythonhosted.org/packages/5f/fb/e3633e5a6bbed1c76bb2e9810dabc2f8467532ffcd29b9aed404b473061a/time_machine-3.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c1aee29bc54356f248d5d7dfdd131e12ca825e850a08c0ebdb022266d073013", size = 38952, upload-time = "2025-12-17T23:32:30.031Z" }, + { url = "https://files.pythonhosted.org/packages/82/3d/02e9fb2526b3d6b1b45bc8e4d912d95d1cd699d1a3f6df985817d37a0600/time_machine-3.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c8ed2224f09d25b1c2fc98683613aca12f90f682a427eabb68fc824d27014e4a", size = 39829, upload-time = "2025-12-17T23:32:31.075Z" }, + { url = "https://files.pythonhosted.org/packages/85/c8/c14265212436da8e0814c45463987b3f57de3eca4de023cc2eabb0c62ef3/time_machine-3.2.0-cp313-cp313t-win32.whl", hash = "sha256:3498719f8dab51da76d29a20c1b5e52ee7db083dddf3056af7fa69c1b94e1fe6", size = 17852, upload-time = "2025-12-17T23:32:32.079Z" }, + { url = "https://files.pythonhosted.org/packages/1d/bc/8acb13cf6149f47508097b158a9a8bec9ec4530a70cb406124e8023581f5/time_machine-3.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e0d90bee170b219e1d15e6a58164aa808f5170090e4f090bd0670303e34181b1", size = 18918, upload-time = "2025-12-17T23:32:33.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/87/c443ee508c2708fd2514ccce9052f5e48888783ce690506919629ebc8eb0/time_machine-3.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:051de220fdb6e20d648111bbad423d9506fdbb2e44d4429cef3dc0382abf1fc2", size = 17261, upload-time = "2025-12-17T23:32:34.446Z" }, + { url = "https://files.pythonhosted.org/packages/61/70/b4b980d126ed155c78d1879c50d60c8dcbd47bd11cb14ee7be50e0dfc07f/time_machine-3.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1398980c017fe5744d66f419e0115ee48a53b00b146d738e1416c225eb610b82", size = 19303, upload-time = "2025-12-17T23:32:35.796Z" }, + { url = "https://files.pythonhosted.org/packages/73/73/eaa33603c69a68fe2b6f54f9dd75481693d62f1d29676531002be06e2d1c/time_machine-3.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4f8f4e35f4191ef70c2ab8ff490761ee9051b891afce2bf86dde3918eb7b537b", size = 15431, upload-time = "2025-12-17T23:32:37.244Z" }, + { url = "https://files.pythonhosted.org/packages/76/10/b81e138e86cc7bab40cdb59d294b341e172201f4a6c84bb0ec080407977a/time_machine-3.2.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6db498686ecf6163c5aa8cf0bcd57bbe0f4081184f247edf3ee49a2612b584f9", size = 33206, upload-time = "2025-12-17T23:32:38.713Z" }, + { url = "https://files.pythonhosted.org/packages/d3/72/4deab446b579e8bd5dca91de98595c5d6bd6a17ce162abf5c5f2ce40d3d8/time_machine-3.2.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:027c1807efb74d0cd58ad16524dec94212fbe900115d70b0123399883657ac0f", size = 34792, upload-time = "2025-12-17T23:32:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/2c/39/439c6b587ddee76d533fe972289d0646e0a5520e14dc83d0a30aeb5565f7/time_machine-3.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92432610c05676edd5e6946a073c6f0c926923123ce7caee1018dc10782c713d", size = 36187, upload-time = "2025-12-17T23:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/4b/db/2da4368db15180989bab83746a857bde05ad16e78f326801c142bb747a06/time_machine-3.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c25586b62480eb77ef3d953fba273209478e1ef49654592cd6a52a68dfe56a67", size = 34855, upload-time = "2025-12-17T23:32:42.817Z" }, + { url = "https://files.pythonhosted.org/packages/88/84/120a431fee50bc4c241425bee4d3a4910df4923b7ab5f7dff1bf0c772f08/time_machine-3.2.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6bf3a2fa738d15e0b95d14469a0b8ea42635467408d8b490e263d5d45c9a177f", size = 33222, upload-time = "2025-12-17T23:32:43.94Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ea/89cfda82bb8c57ff91bb9a26751aa234d6d90e9b4d5ab0ad9dce0f9f0329/time_machine-3.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ce76b82276d7ad2a66cdc85dad4df19d1422b69183170a34e8fbc4c3f35502f7", size = 34270, upload-time = "2025-12-17T23:32:45.037Z" }, + { url = "https://files.pythonhosted.org/packages/8a/aa/235357da4f69a51a8d35fcbfcfa77cdc7dc24f62ae54025006570bda7e2d/time_machine-3.2.0-cp314-cp314-win32.whl", hash = "sha256:14d6778273c543441863dff712cd1d7803dee946b18de35921eb8df10714539d", size = 17544, upload-time = "2025-12-17T23:32:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/6c8405a7276be79693b792cff22ce41067ec05db26a7d02f2d5b06324434/time_machine-3.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbf821da96dbc80d349fa9e7c36e670b41d68a878d28c8850057992fed430eef", size = 18423, upload-time = "2025-12-17T23:32:47.468Z" }, + { url = "https://files.pythonhosted.org/packages/d9/03/a3cf419e20c35fc203c6e4fed48b5b667c1a2b4da456d9971e605f73ecef/time_machine-3.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:71c75d71f8e68abc8b669bca26ed2ddd558430a6c171e32b8620288565f18c0e", size = 17050, upload-time = "2025-12-17T23:32:48.91Z" }, + { url = "https://files.pythonhosted.org/packages/86/a1/142de946dc4393f910bf4564b5c3ba819906e1f49b06c9cb557519c849e4/time_machine-3.2.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4e374779021446fc2b5c29d80457ec9a3b1a5df043dc2aae07d7c1415d52323c", size = 19991, upload-time = "2025-12-17T23:32:49.933Z" }, + { url = "https://files.pythonhosted.org/packages/ee/62/7f17def6289901f94726921811a16b9adce46e666362c75d45730c60274f/time_machine-3.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:122310a6af9c36e9a636da32830e591e7923e8a07bdd0a43276c3a36c6821c90", size = 15707, upload-time = "2025-12-17T23:32:50.969Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d3/3502fb9bd3acb159c18844b26c43220201a0d4a622c0c853785d07699a92/time_machine-3.2.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba3eeb0f018cc362dd8128befa3426696a2e16dd223c3fb695fde184892d4d8c", size = 39207, upload-time = "2025-12-17T23:32:52.033Z" }, + { url = "https://files.pythonhosted.org/packages/5a/be/8b27f4aa296fda14a5a2ad7f588ddd450603c33415ab3f8e85b2f1a44678/time_machine-3.2.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:77d38ba664b381a7793f8786efc13b5004f0d5f672dae814430445b8202a67a6", size = 40764, upload-time = "2025-12-17T23:32:53.167Z" }, + { url = "https://files.pythonhosted.org/packages/42/cd/fe4c4e5c8ab6d48fab3624c32be9116fb120173a35fe67e482e5cf68b3d2/time_machine-3.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f09abeb8f03f044d72712207e0489a62098ad3ad16dac38927fcf80baca4d6a7", size = 43508, upload-time = "2025-12-17T23:32:54.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/28/5a3ba2fce85b97655a425d6bb20a441550acd2b304c96b2c19d3839f721a/time_machine-3.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6b28367ce4f73987a55e230e1d30a57a3af85da8eb1a140074eb6e8c7e6ef19f", size = 41712, upload-time = "2025-12-17T23:32:55.781Z" }, + { url = "https://files.pythonhosted.org/packages/81/58/e38084be7fdabb4835db68a3a47e58c34182d79fc35df1ecbe0db2c5359f/time_machine-3.2.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:903c7751c904581da9f7861c3015bed7cdc40047321291d3694a3cdc783bbca3", size = 38939, upload-time = "2025-12-17T23:32:56.867Z" }, + { url = "https://files.pythonhosted.org/packages/40/d0/ad3feb0a392ef4e0c08bc32024950373ddc0669002cbdcbb9f3bf0c2d114/time_machine-3.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:528217cad85ede5f85c8bc78b0341868d3c3cfefc6ecb5b622e1cacb6c73247b", size = 39837, upload-time = "2025-12-17T23:32:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/5b/9e/5f4b2ea63b267bd78f3245e76f5528836611b5f2d30b5e7300a722fe4428/time_machine-3.2.0-cp314-cp314t-win32.whl", hash = "sha256:75724762ffd517e7e80aaec1fad1ff5a7414bd84e2b3ee7a0bacfeb67c14926e", size = 18091, upload-time = "2025-12-17T23:32:59.403Z" }, + { url = "https://files.pythonhosted.org/packages/39/6f/456b1f4d2700ae02b19eba830f870596a4b89b74bac3b6c80666f1b108c5/time_machine-3.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2526abbd053c5bca898d1b3e7898eec34626b12206718d8c7ce88fd12c1c9c5c", size = 19208, upload-time = "2025-12-17T23:33:00.488Z" }, + { url = "https://files.pythonhosted.org/packages/2f/22/8063101427ecd3d2652aada4d21d0876b07a3dc789125bca2ee858fec3ed/time_machine-3.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7f2fb6784b414edbe2c0b558bfaab0c251955ba27edd62946cce4a01675a992c", size = 17359, upload-time = "2025-12-17T23:33:01.54Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" From 3f686ef2a7e3324bc8615aeb79e009e093533c59 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Sun, 19 Apr 2026 18:36:31 +0200 Subject: [PATCH 153/171] Fix linting issues --- pyproject.toml | 1 + src/aeonlib/salt/models/block_models.py | 12 ++++---- src/aeonlib/salt/models/hrs_models.py | 6 ++-- src/aeonlib/salt/models/nirwals_models.py | 8 ++--- src/aeonlib/salt/models/request_models.py | 2 +- src/aeonlib/salt/models/rss_models.py | 36 +++++++++++----------- src/aeonlib/salt/models/salticam_models.py | 8 ++--- src/aeonlib/salt/models/target_models.py | 4 +-- uv.lock | 2 ++ 9 files changed, 41 insertions(+), 38 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1fba656..a8b1ded 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ codegen = [ ] dev = [ + "lxml-stubs>=0.5.1", "pytest>=8.3.5", "time-machine>=3.2.0", ] diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index d6a488c..0fc7322 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -3,7 +3,7 @@ from __future__ import annotations import uuid -from typing import Annotated, Literal, Self +from typing import Annotated, Literal, Self, TypeAlias import astropy.units as u from pydantic import ( @@ -30,10 +30,10 @@ from aeonlib.salt.validators import GreaterEqual, LessEqual, check_in_visibility_range -Instrument = Salticam | Rss | Hrs | Nirwals +Instrument: TypeAlias = Salticam | Rss | Hrs | Nirwals -class Block(BaseModel, validate_assignment=True): +class Block(BaseModel, validate_assignment=True): # type: ignore """ A block for SALT. @@ -137,7 +137,7 @@ def check_max_num_visits_is_at_least_num_visits(self) -> Self: return self -class Constraints(BaseModel, validate_assignment=True): +class Constraints(BaseModel, validate_assignment=True): # type: ignore """ Observing constraints. @@ -176,7 +176,7 @@ class Constraints(BaseModel, validate_assignment=True): max_seeing: PositiveFloat -class Acquisition(BaseModel, validate_assignment=True): +class Acquisition(BaseModel, validate_assignment=True): # type: ignore """ An acquisition. @@ -240,7 +240,7 @@ def check_do_not_flip_position_angle(self): return self -class ReferenceStar(BaseModel, validate_assignment=True): +class ReferenceStar(BaseModel, validate_assignment=True): # type: ignore """ A reference star on which to acquire. diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py index cc53082..b6c6ae1 100644 --- a/src/aeonlib/salt/models/hrs_models.py +++ b/src/aeonlib/salt/models/hrs_models.py @@ -15,7 +15,7 @@ from aeonlib.types import Angle -class Hrs(BaseModel, validate_assignment=True): +class Hrs(BaseModel, validate_assignment=True): # type: ignore """ An HRS setup. @@ -55,13 +55,13 @@ class Hrs(BaseModel, validate_assignment=True): blue_arm: HrsDetector red_arm: HrsDetector - @computed_field + @computed_field # type: ignore @property def prv_calibration(self) -> HrsPrvCalibration | None: return "ThAr" if self.mode == "high stability" else None -class HrsDetector(BaseModel, validate_assignment=True): +class HrsDetector(BaseModel, validate_assignment=True): # type: ignore """ An HRS detector setup. diff --git a/src/aeonlib/salt/models/nirwals_models.py b/src/aeonlib/salt/models/nirwals_models.py index 79127dc..b842fbc 100644 --- a/src/aeonlib/salt/models/nirwals_models.py +++ b/src/aeonlib/salt/models/nirwals_models.py @@ -32,7 +32,7 @@ from aeonlib.types import Angle -class Nirwals(BaseModel, validate_assignment=True): +class Nirwals(BaseModel, validate_assignment=True): # type: ignore """ A NIRWALS configuration. @@ -85,7 +85,7 @@ class Nirwals(BaseModel, validate_assignment=True): @classmethod def check_articulation_angle(cls, angle: Angle) -> Angle: error = "The articulation angle must be a multiple of 0.5 degress between 0 and 100 degrees (both inclusive" - degrees = angle.to(u.deg).value + degrees = angle.to(u.deg).value # type: ignore if degrees < 0 or degrees > 100: raise ValueError(error) @@ -97,7 +97,7 @@ def check_articulation_angle(cls, angle: Angle) -> Angle: return angle -class NirwalsDitherPatternStep(BaseModel, validate_assignment=True): +class NirwalsDitherPatternStep(BaseModel, validate_assignment=True): # type: ignore """ A step in a NIRWALS dither pattern. @@ -158,7 +158,7 @@ class NirwalsDitherPatternStep(BaseModel, validate_assignment=True): num_reads: Literal[1] = 1 num_ramps: Literal[1] = 1 - @computed_field + @computed_field # type: ignore @property def num_groups(self) -> int: """ diff --git a/src/aeonlib/salt/models/request_models.py b/src/aeonlib/salt/models/request_models.py index fc6281c..d2fee4f 100644 --- a/src/aeonlib/salt/models/request_models.py +++ b/src/aeonlib/salt/models/request_models.py @@ -15,7 +15,7 @@ ) -class Request(BaseModel, validate_assignment=True): +class Request(BaseModel, validate_assignment=True): # type: ignore """ An observation request for SALT. diff --git a/src/aeonlib/salt/models/rss_models.py b/src/aeonlib/salt/models/rss_models.py index 61ac7a8..e0e1339 100644 --- a/src/aeonlib/salt/models/rss_models.py +++ b/src/aeonlib/salt/models/rss_models.py @@ -44,7 +44,7 @@ from aeonlib.salt.validators import GreaterEqual, GreaterThan, LessEqual -class Rss(BaseModel, validate_assignment=True): +class Rss(BaseModel, validate_assignment=True): # type: ignore """ An RSS configuration. @@ -102,7 +102,7 @@ class Rss(BaseModel, validate_assignment=True): dither_pattern: RssDitherPattern | None -class RssImaging(BaseModel, validate_assignment=True): +class RssImaging(BaseModel, validate_assignment=True): # type: ignore """ An RSS imaging configuration. @@ -144,7 +144,7 @@ def serialize_filter(value: str) -> str: ) -class RssSpectroscopy(BaseModel, validate_assignment=True): +class RssSpectroscopy(BaseModel, validate_assignment=True): # type: ignore """ An RSS spectroscopy configuration. @@ -185,11 +185,11 @@ class RssSpectroscopy(BaseModel, validate_assignment=True): include_arc: bool = True request_spectrophotometric_standard: bool = False - @computed_field + @computed_field # type: ignore @property def articulation_station(self) -> int: """Return the articulation station.""" - degrees = self.articulation_angle.to(u.deg).value + degrees = self.articulation_angle.to(u.deg).value # type: ignore if degrees < 1: return 0 return round((degrees - 1) / 0.75) @@ -198,7 +198,7 @@ def articulation_station(self) -> int: @classmethod def check_articulation_angle(cls, angle: Angle) -> Angle: error = "The articulation angle must either be 0 deg or a value 1.75 deg + (n - 1) * 0.75 deg with 1 <= n <= 132." - degrees = angle.to(u.deg).value + degrees = angle.to(u.deg).value # type: ignore if degrees < 0: raise ValueError(error) @@ -266,7 +266,7 @@ class RssSlitMaskIFUSpectroscopy(RssSpectroscopy): slit_mask_ifu: Annotated[RssSlitMaskIFU, LowerCaseValidator, UpperCaseSerializer] -class RssPolarimetry(BaseModel, validate_assignment=True): +class RssPolarimetry(BaseModel, validate_assignment=True): # type: ignore """ An RSS polarimetry setup. @@ -311,21 +311,21 @@ class RssPolarimetry(BaseModel, validate_assignment=True): @staticmethod def validate_pattern_before(value: _WavePlatePattern) -> _WavePlatePattern: if isinstance(value, str): - return value.lower() + return value.lower() # type: ignore return value @staticmethod def validate_pattern_after(value: _WavePlatePattern) -> _WavePlatePattern: if value == "linear": - value = LINEAR_POLARIMETRY_PATTERN + value = LINEAR_POLARIMETRY_PATTERN # type: ignore elif value == "linear hi": - value = LINEAR_HI_POLARIMETRY_PATTERN + value = LINEAR_HI_POLARIMETRY_PATTERN # type: ignore elif value == "circular": - value = CIRCULAR_POLARIMETRY_PATTERN + value = CIRCULAR_POLARIMETRY_PATTERN # type: ignore elif value == "circular hi": - value = CIRCULAR_HI_POLARIMETRY_PATTRERN + value = CIRCULAR_HI_POLARIMETRY_PATTRERN # type: ignore elif value == "all-stokes": - value = ALL_STOKES_POLARIMETRY_PATTERN + value = ALL_STOKES_POLARIMETRY_PATTERN # type: ignore if isinstance(value, str): raise ValueError(f"Unsupported string value: {value}") @@ -352,17 +352,17 @@ def _check_pattern_step(step: tuple[Angle | None, Angle | None]) -> None: # Check that the ratio of the angle and 11.25 deg is (very close to) an # integer. - x = (angle.to(u.deg) / 11.25).value + x = (angle.to(u.deg) / 11.25).value # type: ignore if abs(round(x) - x) > 1e-6: raise ValueError(error) @staticmethod - def _check_angle_values(value: _WavePlatePattern) -> _WavePlatePattern: + def _check_angle_values(value: _WavePlatePattern) -> None: for step in value: - RssPolarimetry._check_pattern_step(step) + RssPolarimetry._check_pattern_step(step) # type: ignore -class RssDetector(BaseModel, validate_assignment=True): +class RssDetector(BaseModel, validate_assignment=True): # type: ignore """ An Rss detector setup. @@ -401,7 +401,7 @@ class RssDetector(BaseModel, validate_assignment=True): ] = None -class RssDitherPattern(BaseModel, validate_assignment=True): +class RssDitherPattern(BaseModel, validate_assignment=True): # type: ignore """ A dither pattern for RSS. diff --git a/src/aeonlib/salt/models/salticam_models.py b/src/aeonlib/salt/models/salticam_models.py index 3a3bc28..53db173 100644 --- a/src/aeonlib/salt/models/salticam_models.py +++ b/src/aeonlib/salt/models/salticam_models.py @@ -17,7 +17,7 @@ from aeonlib.salt.validators import GreaterEqual, LessEqual -class Salticam(BaseModel, validate_assignment=True): +class Salticam(BaseModel, validate_assignment=True): # type: ignore """ A Salticam instrument configuration. @@ -61,7 +61,7 @@ class Salticam(BaseModel, validate_assignment=True): include_flat: bool -class SalticamFilterSequenceStep(BaseModel, validate_assignment=True): +class SalticamFilterSequenceStep(BaseModel, validate_assignment=True): # type: ignore """ A step in a filter sequence. @@ -78,7 +78,7 @@ class SalticamFilterSequenceStep(BaseModel, validate_assignment=True): exposure_time: PositiveDuration -class SalticamDetector(BaseModel, validate_assignment=True): +class SalticamDetector(BaseModel, validate_assignment=True): # type: ignore """ A Salticam detector setup. @@ -98,7 +98,7 @@ class SalticamDetector(BaseModel, validate_assignment=True): num_prebinned_columns: Annotated[int, GreaterEqual(1), LessEqual(9)] -class SalticamDitherPattern(BaseModel, validate_assignment=True): +class SalticamDitherPattern(BaseModel, validate_assignment=True): # type: ignore """ A dither pattern for Salticam. diff --git a/src/aeonlib/salt/models/target_models.py b/src/aeonlib/salt/models/target_models.py index 80f39ee..e8b7081 100644 --- a/src/aeonlib/salt/models/target_models.py +++ b/src/aeonlib/salt/models/target_models.py @@ -12,7 +12,7 @@ from aeonlib.salt.validators import check_in_visibility_range -class SaltSiderealTarget(SiderealTarget, validate_assignment=True): +class SaltSiderealTarget(SiderealTarget, validate_assignment=True): # type: ignore """ A sidereal target to observe with SALT. @@ -58,7 +58,7 @@ def check_declination_viewable(cls, value: astropy.coordinates.Angle): return check_in_visibility_range(value) -class MagnitudeRange(BaseModel, validate_assignment=True): +class MagnitudeRange(BaseModel, validate_assignment=True): # type: ignore """ A magnitude range. diff --git a/uv.lock b/uv.lock index 7365e1c..be7183d 100644 --- a/uv.lock +++ b/uv.lock @@ -34,6 +34,7 @@ codegen = [ { name = "textcase" }, ] dev = [ + { name = "lxml-stubs" }, { name = "pytest" }, { name = "time-machine" }, ] @@ -60,6 +61,7 @@ codegen = [ { name = "textcase", specifier = ">=0.2.1" }, ] dev = [ + { name = "lxml-stubs", specifier = ">=0.5.1" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "time-machine", specifier = ">=3.2.0" }, ] From 79ee33b4536c1205d3783ec6f6091cc4ec38d789 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 27 Apr 2026 15:19:49 +0200 Subject: [PATCH 154/171] Add a section on SALT to the README --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ce016aa..c8e0d0e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ A suite of modules to enable TDA/MMA observations [issues](https://github.com/AEONplus/AEONlib/issues) # Configuration + Many of the facilities and services accessed by AEONlib require specific configuration such as api keys, urls, etc. All configuration can be supplied by either supplying a .env file or setting environmental variables in the execution environment. @@ -33,8 +34,8 @@ Environmental variables take precedence over .env files. See the [pydantic-settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) documentation for more details. - # Testing + This project uses [pytest](https://docs.pytest.org/) to run tests: ```bash @@ -59,6 +60,7 @@ pytest -m "not side_effect" CI does not run tests marked as online. ## Viewing logs during tests + Aeonlib turns on the Pytest [Live Logging](https://docs.pytest.org/en/stable/how-to/logging.html#live-logs) feature. By default any logging calls with a level above `WARNING` will be displayed to the console @@ -70,9 +72,11 @@ pytest -m online --log-cli-level=debug ``` # Linting + All code is formatted via [ruff](https://astral.sh/ruff). # Code Generation + Las Cumbres Observatory [instrument classes](src/aeonlib/ocs/lco/instruments.py) are generated via the [generator.py](codegen/lco/generator.py) script. This script takes as input the [OCS instruments api](https://observe.lco.global/api/instruments/) @@ -105,6 +109,7 @@ LCO instruments definition file: ```bash curl https://observe.lco.global/api/instruments/ | codegen/lco/generator.py {facility} > src/aeonlib/ocs/lco/instruments.py ``` + # Supported Facilities This list is a work in progress. @@ -112,15 +117,18 @@ This list is a work in progress. ## Las Cumbres Observatory (LCO) ### Dependency group + Las Cumbres Observatory requires no additional dependency groups to be installed. ### Configuration Values + See [configuration](#configuration) for instructions on setting these values. ```python lco_token: str = "" lco_api_root: str = "https://observe.lco.global/api/" ``` + ### Helpful links * [LCO Observation Portal](https://observe.lco.global/) @@ -132,15 +140,18 @@ lco_api_root: str = "https://observe.lco.global/api/" SOAR is functionally the same as LCO, but has its own set of instruments and can be configured separately. ### Dependency group + SOAR requires no additional dependency groups to be installed. ### Configuration Values + See [configuration](#configuration) for instructions on setting these values. ```python soar_token: str = "" soar_api_root: str = "https://observe.lco.global/api/" ``` + Note: the soar API token will default to the same value as lco_token, if it is set. ## BLANCO @@ -148,15 +159,18 @@ Note: the soar API token will default to the same value as lco_token, if it is s BLANCO is functionally the same as LCO, but has its own set of instruments and can be configured separately. ### Dependency group + BLANCO requires no additional dependency groups to be installed. ### Configuration Values + See [configuration](#configuration) for instructions on setting these values. ```python blanco_token: str = "" blanco_api_root: str = "https://observe.lco.global/api/" ``` + Note: the blanco API token will default to the same value as lco_token, if it is set. ## ESO (European Southern Observatory) @@ -164,7 +178,9 @@ Note: the blanco API token will default to the same value as lco_token, if it is Full documentation: TODO ### Dependency Group + To use the ESO facility, you must install the `eso` group: + ```bash pip install aeonlib[eso] uv sync --extra eso @@ -172,6 +188,7 @@ poetry install --with eso ``` ### Configuration Values + See [configuration](#configuration) for instructions on setting these values. ```python @@ -185,17 +202,20 @@ eso_password: str = "" * [ESO Phase 2 API](https://www.eso.org/sci/observing/phase2/p2intro/Phase2API.html) * [ESO Phase 2 Demo Application](https://www.eso.org/p2demo/home) - ## LT (Liverpool Telescope) ### Dependency Group + To use the LT facility, you must install the `lt` group: + ```bash pip install aeonlib[lt] uv sync --extra lt poetry install --with lt ``` + ### Configuration Values + See [configuration](#configuration) for instructions on setting these values. ```python @@ -204,17 +224,43 @@ lt_password: str = "" lt_host: str = "" lt_port: str = "" ``` + ### Helpful links -* [LT Phase 2 Information](https://telescope.livjm.ac.uk/PropInst/Phase2/) +* [LT Phase 2 Information](https://telescope.livjm.ac.uk/PropInst/Phase2/) ## SAAO (South African Astronomical Observatory) ### Configuration Values + ```python saao_token: str = "" - saao_api_root: str = "https://ocsio.saao.ac.za/api/" +saao_api_root: str = "https://ocsio.saao.ac.za/api/" ``` ### Helpful links + * [SAAO Observatory Control System](https://ocsio.saao.ac.za/create) + +## SALT (Southern African Large Telescope) + +### Dependency Group + +To use the SALT facility, you must install the `salt` group: + +```bash +pip install aeonlib[salt] +uv sync --extra salt +poetry install --with salt +``` + +### Configuration values + +See [configuration](#configuration) for instructions on setting these values. + +```python +salt_username: str = "" +salt_password = "" +``` + +The username and password are those you would use for the [SALT Web Manager](https://www.salt.ac.za/wm/). From 086f23f971803500187a3ae756d320eb31c9ca1b Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 27 Apr 2026 15:20:17 +0200 Subject: [PATCH 155/171] Add PyAstroSALT as a dependency --- pyproject.toml | 1 + uv.lock | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index a8b1ded..46a1c40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ salt = [ "beautifulsoup4>=4.14.3", "jinja2>=3.1.6", "lxml>=6.0.1", + "pyastrosalt>=0.2.0", ] [tool.pytest.ini_options] diff --git a/uv.lock b/uv.lock index be7183d..3dabe59 100644 --- a/uv.lock +++ b/uv.lock @@ -26,6 +26,7 @@ salt = [ { name = "beautifulsoup4" }, { name = "jinja2" }, { name = "lxml" }, + { name = "pyastrosalt" }, ] [package.dev-dependencies] @@ -49,6 +50,7 @@ requires-dist = [ { name = "lxml", marker = "extra == 'salt'", specifier = ">=6.0.1" }, { name = "lxml-stubs", marker = "extra == 'lt'", specifier = ">=0.5.1" }, { name = "p2api", marker = "extra == 'eso'", specifier = ">=1.0.10" }, + { name = "pyastrosalt", marker = "extra == 'salt'", specifier = ">=0.2.0" }, { name = "pydantic", specifier = ">=2.11.1" }, { name = "pydantic-settings", specifier = ">=2.9.1" }, { name = "suds", marker = "extra == 'lt'", specifier = ">=1.2.0" }, @@ -281,6 +283,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/39/e6042bcb2638650b0005c752c38ea830cbfbcbb1830e4d64d530000aa8dc/cryptography-46.0.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7fab1187b6c6b2f11a326f33b036f7168f5b996aedd0c059f9738915e4e8f53a", size = 4699541, upload-time = "2025-09-17T00:10:06.925Z" }, ] +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -619,6 +630,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pyastrosalt" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "defusedxml" }, + { name = "requests" }, + { name = "types-defusedxml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/08/2a45dac29e5e7d2084425209ebf55c6cc2ef75d54c1b3dd65cb54bb9d09f/pyastrosalt-0.2.0.tar.gz", hash = "sha256:4e6d14eefaa34520fca1ac6f53675053c42deb5526d4d8605a940ea1dca316e4", size = 16204, upload-time = "2026-04-24T17:05:04.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/74/0ba659828893ce394ba999f64dabbd0f051039d7ab474363394d54c97b45/pyastrosalt-0.2.0-py3-none-any.whl", hash = "sha256:fe94f6290c7e2d007256b7e7b615ad7b6c041f347dcf5df8385e572e7c10dd79", size = 11986, upload-time = "2026-04-24T17:05:03.106Z" }, +] + [[package]] name = "pycparser" version = "2.23" @@ -913,6 +938,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/22/8063101427ecd3d2652aada4d21d0876b07a3dc789125bca2ee858fec3ed/time_machine-3.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7f2fb6784b414edbe2c0b558bfaab0c251955ba27edd62946cce4a01675a992c", size = 17359, upload-time = "2025-12-17T23:33:01.54Z" }, ] +[[package]] +name = "types-defusedxml" +version = "0.7.0.20260408" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/af/d324da5ffbf0af40477533a09ee6c902de335c445a8dcc88c58f62af6e5f/types_defusedxml-0.7.0.20260408.tar.gz", hash = "sha256:f35377d59344f98b57f9bf319cff2107aac35f9e4d42f9ed6cfeeafacffadb00", size = 10638, upload-time = "2026-04-08T04:26:12.239Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/68/7570cfb818d6a5b3ff964114527e28e360eccf18329b457f057a18596e64/types_defusedxml-0.7.0.20260408-py3-none-any.whl", hash = "sha256:2d68db82412170b91b3e490b7c118a4f4e5a27756a126e2453f629c8d514b106", size = 13435, upload-time = "2026-04-08T04:26:11.347Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" From 8a011492b18d43db309003a3d53ff4e3f8962321 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 27 Apr 2026 15:23:07 +0200 Subject: [PATCH 156/171] Add SALT configuration settings --- src/aeonlib/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/aeonlib/conf.py b/src/aeonlib/conf.py index f5a1d24..e02d58a 100644 --- a/src/aeonlib/conf.py +++ b/src/aeonlib/conf.py @@ -35,5 +35,9 @@ class Settings(BaseSettings): lt_host: str = "" lt_port: str = "" + # Southern African Large Telescope + salt_username: str = "" + salt_password: str = "" + settings = Settings() From bd390a41d77ba7142496ff3181e4eb40a3446ada Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 27 Apr 2026 15:53:35 +0200 Subject: [PATCH 157/171] Handle missing windows --- .../salt/models/serialize/templates/block.xml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/aeonlib/salt/models/serialize/templates/block.xml b/src/aeonlib/salt/models/serialize/templates/block.xml index ff52e94..1c173d3 100644 --- a/src/aeonlib/salt/models/serialize/templates/block.xml +++ b/src/aeonlib/salt/models/serialize/templates/block.xml @@ -95,12 +95,14 @@ {% endwith %} - {% for window in block.windows %} - - {{ window.start | utc }} - {{ window.end | utc }} - - {% endfor %} + {% if block.windows %} + {% for window in block.windows %} + + {{ window.start | utc }} + {{ window.end | utc }} + + {% endfor %} + {% endif %} Normal HTML From 470119e22eff09d076fdb241a22a42baa0203db5 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 27 Apr 2026 17:37:00 +0200 Subject: [PATCH 158/171] Fix name of the file containing the submitted blocks --- src/aeonlib/salt/models/request_models.py | 2 +- tests/salt/models/test_request_models.py | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/aeonlib/salt/models/request_models.py b/src/aeonlib/salt/models/request_models.py index d2fee4f..6ab0d18 100644 --- a/src/aeonlib/salt/models/request_models.py +++ b/src/aeonlib/salt/models/request_models.py @@ -94,6 +94,6 @@ def export(self, out: pathlib.Path | os.PathLike | str | BinaryIO) -> None: block_submission_xml = replace_attachment_paths( block_submission_xml, replacements ) - archive.writestr("BlockSubmission.xml", block_submission_xml) + archive.writestr("Blocks.xml", block_submission_xml) for path, zip_path in replacements.items(): archive.write(path, zip_path) diff --git a/tests/salt/models/test_request_models.py b/tests/salt/models/test_request_models.py index 009f533..98f6c9b 100644 --- a/tests/salt/models/test_request_models.py +++ b/tests/salt/models/test_request_models.py @@ -160,16 +160,14 @@ def test_export(self, base_block, base_rss, base_rss_multi_object_spectroscopy): # Check the content of the generated zip file. zip_content.seek(0) with zipfile.ZipFile(zip_content) as archive: - block_submission = archive.read("BlockSubmission.xml").decode( - encoding="utf-8" - ) + block_submission = archive.read("Blocks.xml").decode(encoding="utf-8") for file in archive.namelist(): - if file != "BlockSubmission.xml": + if file != "Blocks.xml": assert file.startswith("Included/") assert ( - file.endswith(".pdf") - or file.endswith(".png") - or file.endswith(".rsmt") + file.endswith(".pdf") + or file.endswith(".png") + or file.endswith(".rsmt") ) assert file in block_submission From 47cc917892ba93b6607da7a828a9361a838a19f9 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 27 Apr 2026 20:10:02 +0200 Subject: [PATCH 159/171] Remove wrong type from Hrs documentation --- src/aeonlib/salt/models/hrs_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeonlib/salt/models/hrs_models.py b/src/aeonlib/salt/models/hrs_models.py index b6c6ae1..f9762a6 100644 --- a/src/aeonlib/salt/models/hrs_models.py +++ b/src/aeonlib/salt/models/hrs_models.py @@ -40,9 +40,9 @@ class Hrs(BaseModel, validate_assignment=True): # type: ignore fibre_separation The angle between the target and sky fibres. This must be between 16 and 63 arcseconds (both inclusive). - blue_arm: None + blue_arm The detector setup for the red arm. - red_arm: None + red_arm The detector setup for the blue arm. """ From b39f7e7f8dd9472cb52f7ca8b12647188f3e4e4c Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 27 Apr 2026 21:08:07 +0200 Subject: [PATCH 160/171] Handle the case of no arc request correctly --- src/aeonlib/salt/models/serialize/templates/rss.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/aeonlib/salt/models/serialize/templates/rss.xml b/src/aeonlib/salt/models/serialize/templates/rss.xml index 04efdc9..c85775b 100644 --- a/src/aeonlib/salt/models/serialize/templates/rss.xml +++ b/src/aeonlib/salt/models/serialize/templates/rss.xml @@ -38,6 +38,8 @@ + {% else %} + true {% endif %} {% if rss.configuration.request_spectrophotometric_standard %} From d12e1ee3a3be87bda379073c4a1a486916eea00c Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 27 Apr 2026 21:08:55 +0200 Subject: [PATCH 161/171] Move conftest.py --- tests/salt/{models => }/conftest.py | 6 ++-- tests/salt/models/test_request_models.py | 42 ++++++++++-------------- 2 files changed, 19 insertions(+), 29 deletions(-) rename tests/salt/{models => }/conftest.py (96%) diff --git a/tests/salt/models/conftest.py b/tests/salt/conftest.py similarity index 96% rename from tests/salt/models/conftest.py rename to tests/salt/conftest.py index 00c61ad..f3d67ce 100644 --- a/tests/salt/models/conftest.py +++ b/tests/salt/conftest.py @@ -88,9 +88,7 @@ def base_constraints(): @pytest.fixture() def base_acquisition(): - finder_chart = ( - pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" - ) + finder_chart = pathlib.Path(__file__).parent / "data" / "dummy_finder_chart_1.pdf" return Acquisition(finder_charts=[finder_chart], position_angle=45 * u.deg) @@ -173,7 +171,7 @@ def base_rss_longslit_spectroscopy(base_rss_spectroscopy): def base_rss_multi_object_spectroscopy(base_rss_spectroscopy): return RssMultiObjectSpectroscopy( **base_rss_spectroscopy.model_dump(), - mask=pathlib.Path(__file__).parent.parent / "data" / "dummy_rss_mos_mask.rsmt", + mask=pathlib.Path(__file__).parent / "data" / "dummy_rss_mos_mask.rsmt", ) diff --git a/tests/salt/models/test_request_models.py b/tests/salt/models/test_request_models.py index 98f6c9b..81dc165 100644 --- a/tests/salt/models/test_request_models.py +++ b/tests/salt/models/test_request_models.py @@ -19,7 +19,7 @@ def test_no_blocks(self): assert exc_info.value.errors()[0]["type"] == "too_short" def test_no_attachments( - self, base_request, base_block, base_rss, base_rss_longslit_spectroscopy + self, base_request, base_block, base_rss, base_rss_longslit_spectroscopy ): """Test the case that the request includes no attachments.""" request = base_request @@ -34,20 +34,17 @@ def test_no_attachments( assert request.attachments() == set() def test_multiple_attachments( - self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy + self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy ): """Test the case that the request includes multiple attachments.""" finder_chart_1 = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" ) finder_chart_2 = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" ) mos_mask = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_rss_mos_mask.rsmt" + pathlib.Path(__file__).parent.parent / "data" / "dummy_rss_mos_mask.rsmt" ) request = base_request block = base_block @@ -66,27 +63,25 @@ def test_multiple_attachments( } def test_duplicate_attachments( - self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy + self, base_request, base_block, base_rss, base_rss_multi_object_spectroscopy ): """Test the case that the request uses the same attachment multiple times.""" # finder_chart_1a, finder_chart_1b and finder_chart_1c denote the sane file finder_chart_1a = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" ) finder_chart_1b = ( - pathlib.Path(__file__).parent.parent - / "data/../data" - / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent + / "data/../data" + / "dummy_finder_chart_1.pdf" ) finder_chart_1c = ( - pathlib.Path(__file__).parent.parent - / "data/../../salt/data" - / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent + / "data/../../salt/data" + / "dummy_finder_chart_1.pdf" ) finder_chart_2 = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_2.pdf" ) mos_mask = finder_chart_1c # as we are testing for duplicates request = base_request @@ -122,16 +117,13 @@ def test_export(self, base_block, base_rss, base_rss_multi_object_spectroscopy): # Set up the attachments. finder_chart_1 = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_1.pdf" ) finder_chart_2 = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_finder_chart_3.png" + pathlib.Path(__file__).parent.parent / "data" / "dummy_finder_chart_3.png" ) mos_file = ( - pathlib.Path( - __file__).parent.parent / "data" / "dummy_rss_mos_mask.rsmt" + pathlib.Path(__file__).parent.parent / "data" / "dummy_rss_mos_mask.rsmt" ) # Store the attachment sizes. From 229914e5dfe14f4d08b890df2b3438ba51fd1f21 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 27 Apr 2026 21:09:26 +0200 Subject: [PATCH 162/171] Remove extraneous whitespace in XML --- src/aeonlib/salt/models/util.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/util.py b/src/aeonlib/salt/models/util.py index 1cd1315..203d824 100644 --- a/src/aeonlib/salt/models/util.py +++ b/src/aeonlib/salt/models/util.py @@ -2,6 +2,7 @@ import io import pathlib import uuid +import xml.etree.ElementTree as ET import zoneinfo from typing import Any, Iterable, cast from uuid import uuid4 @@ -319,5 +320,22 @@ def replace_attachment_paths(xml: str, replacements: dict[pathlib.Path, str]) -> f"Path missing in replacements dictionary: {path_text} (resolved: {str(path)}" ) path_element.string = replacements_resolved[path] + xml = soup.prettify() - return soup.prettify() + # BeautifulSoup inserts extraneous whitespace around text in elements, but this + # renders the XML invalid. + return _remove_whitespace(xml) + + +def _remove_whitespace(xml: str) -> str: + # Remove whitespace around text in XML elements. + # Adapted from https://stackoverflow.com/questions/58344879/how-to-output-xml-from-beautifulsoup-without-extraneous-newlines + tree = ET.fromstring(xml) + + # Remove extraneous newlines and whitespace from text elements. + for element in tree.iter(): + if element.text: + element.text = element.text.strip() + + # Return the updated XML. + return ET.tostring(tree).decode("UTF-8") From 699882e6f4e3e7b715f5c5d5522bee8805efd709 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Mon, 27 Apr 2026 21:52:47 +0200 Subject: [PATCH 163/171] Fix the type hint for blocks --- src/aeonlib/salt/models/request_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aeonlib/salt/models/request_models.py b/src/aeonlib/salt/models/request_models.py index 6ab0d18..31596ef 100644 --- a/src/aeonlib/salt/models/request_models.py +++ b/src/aeonlib/salt/models/request_models.py @@ -8,6 +8,7 @@ from annotated_types import MinLen from pydantic import BaseModel, Field +from aeonlib.salt.models import Block from aeonlib.salt.models.util import ( render_template, attachment_path_replacements, @@ -45,7 +46,7 @@ class Request(BaseModel, validate_assignment=True): # type: ignore semester: str = Field(pattern=r"\d{4}-[12]") - blocks: Annotated[list, MinLen(1)] + blocks: Annotated[list[Block], MinLen(1)] def attachments(self) -> set[pathlib.Path]: """ From 3b08519c16111492c2bbd57daed67c7cdcfc4f28 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 28 Apr 2026 17:35:38 +0200 Subject: [PATCH 164/171] Upgrade PyAstroSALT --- pyproject.toml | 4 +- uv.lock | 863 ++++++++++++++++++++++++++++--------------------- 2 files changed, 488 insertions(+), 379 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 46a1c40..155a5e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,11 +39,11 @@ salt = [ "beautifulsoup4>=4.14.3", "jinja2>=3.1.6", "lxml>=6.0.1", - "pyastrosalt>=0.2.0", + "pyastrosalt>=0.2.1", ] [tool.pytest.ini_options] -addopts = ["--import-mode=importlib", "-m not online"] +addopts = ["--import-mode=importlib", "-m not online"] markers = [ "online: Marks tests that run online, for example, validating schemas", "side_effect: Online tests that have side effects such as creating observation requests", diff --git a/uv.lock b/uv.lock index 3dabe59..d53fc25 100644 --- a/uv.lock +++ b/uv.lock @@ -50,7 +50,7 @@ requires-dist = [ { name = "lxml", marker = "extra == 'salt'", specifier = ">=6.0.1" }, { name = "lxml-stubs", marker = "extra == 'lt'", specifier = ">=0.5.1" }, { name = "p2api", marker = "extra == 'eso'", specifier = ">=1.0.10" }, - { name = "pyastrosalt", marker = "extra == 'salt'", specifier = ">=0.2.0" }, + { name = "pyastrosalt", marker = "extra == 'salt'", specifier = ">=0.2.1" }, { name = "pydantic", specifier = ">=2.11.1" }, { name = "pydantic-settings", specifier = ">=2.9.1" }, { name = "suds", marker = "extra == 'lt'", specifier = ">=1.2.0" }, @@ -79,21 +79,20 @@ wheels = [ [[package]] name = "anyio" -version = "4.10.0" +version = "4.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, - { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, ] [[package]] name = "astropy" -version = "7.1.0" +version = "7.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astropy-iers-data" }, @@ -102,31 +101,25 @@ dependencies = [ { name = "pyerfa" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/91/124d020cea78e4e4b6db7ff726c2c2e4a5865293d0a4355d13b0312d99f1/astropy-7.1.0.tar.gz", hash = "sha256:c8f254322295b1b8cf24303d6f155bf7efdb6c1282882b966ce3040eff8c53c5", size = 6976116, upload-time = "2025-05-20T13:40:10.557Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/92/2dce2d48347efc3346d08ca7995b152d242ebd170c571f7c9346468d8427/astropy-7.2.0.tar.gz", hash = "sha256:ae48bc26b1feaeb603cd94bd1fa1aa39137a115fe931b7f13787ab420e8c3070", size = 7057774, upload-time = "2025-11-25T22:36:41.916Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/9a/ed2b35b55e28a6317471b61456d2feda7798b2dd3601e17859620e8eae4c/astropy-7.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e0fec2f4b5265caab68020eaa320704e7ce9433ae8dbea75c300468fed695437", size = 6381273, upload-time = "2025-05-20T13:39:45.481Z" }, - { url = "https://files.pythonhosted.org/packages/5c/45/333bc1072f3b2ac31aec33063bb7122661405a97cb7fec702e95af707bd4/astropy-7.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4bb022f863cf13eefeb406692f58824c0d9bdb1aa36ae786e87c096d8ebdd07", size = 6301716, upload-time = "2025-05-20T13:39:47.339Z" }, - { url = "https://files.pythonhosted.org/packages/58/90/bfb7a1b5d9e3401967e351cf31add576cddf7466d2030cc6f4d1d841a18d/astropy-7.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811a4aedd8fbf6d7611d64d40af1f0c1c1e6621e5992e7e1a7b5fec47dc1fa1", size = 10096600, upload-time = "2025-05-20T13:39:49.169Z" }, - { url = "https://files.pythonhosted.org/packages/fb/69/a34f20db7146912f25e2487c5283f1ae2aed5d24f615fa976439ece35f7e/astropy-7.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:654261df547c150e5b6a022f3785f47a2e547e0cc1c06fbcf6293b5f4e85722a", size = 10160320, upload-time = "2025-05-20T13:39:51.079Z" }, - { url = "https://files.pythonhosted.org/packages/7a/be/0b874f551acbac27ff3d5d73bdf3c0860a8b78c6db574b19e6d0b7d363e4/astropy-7.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6bd25761ba1385bb99a189401fbc486e0884d97129e271b655b6efa956a12a77", size = 10137307, upload-time = "2025-05-20T13:39:53.175Z" }, - { url = "https://files.pythonhosted.org/packages/ea/1c/132241ab2006a52b4809ee82a71fde793c0159916ae45dae374b0412b037/astropy-7.1.0-cp312-cp312-win32.whl", hash = "sha256:f637e39622b23750a12b19ab4642f2e3970f6cb84f2228587725f15bf1d80d03", size = 6152433, upload-time = "2025-05-20T13:39:54.933Z" }, - { url = "https://files.pythonhosted.org/packages/11/39/0a38241008905a9c1048f7b351310deee71cf42e0de1f929e84a10878864/astropy-7.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:06ba650db237557912fdd7bb8ffdd87838e23e58b0fa0001b4d43c2bb5a79412", size = 6279663, upload-time = "2025-05-20T13:39:56.274Z" }, - { url = "https://files.pythonhosted.org/packages/a3/d6/ec68703aff787ca1bb2ed92a109256a6e4d9975aeee0b4bd2b0ab84b2993/astropy-7.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:116149abdebafab193f7ca16427bd6bb4afc111677c78c32f48cffbc0ecb6a18", size = 6375607, upload-time = "2025-05-20T13:39:57.6Z" }, - { url = "https://files.pythonhosted.org/packages/2b/79/0f6eacbe575c3b677d70193c22b68c3a95bec68204ccf53068b657c7ed06/astropy-7.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:734d5e93e76d2e2175f991b17ca130463f2edb56c107fbc07c6532ca461a6ea7", size = 6296640, upload-time = "2025-05-20T13:39:58.973Z" }, - { url = "https://files.pythonhosted.org/packages/c7/c1/06bd8bd17b2302c05d75079ca89d79825dede63c00876e439ec91f120405/astropy-7.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fd94f755f2e9104c5caf0afe69175d2620708828233d387c1dad0044e79f543", size = 10030192, upload-time = "2025-05-20T13:40:01.129Z" }, - { url = "https://files.pythonhosted.org/packages/17/ce/f9e103e6be3d801cadffef8eb814bd10e4de3603b9ba9f97dd41c6a59bde/astropy-7.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49effea6de855dcdba09a211d51ca279766c66284c8db3d911b4523bbd96eae9", size = 10079889, upload-time = "2025-05-20T13:40:03.076Z" }, - { url = "https://files.pythonhosted.org/packages/a3/fe/605a09e74b03ea2981632cd9a9a3720bd8d232ddaa729fc63ce46d538218/astropy-7.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:52709e6bbe65c6d6c2a342506ad8c2801ff2aed80cc0a5d7817387122e0ac616", size = 10084024, upload-time = "2025-05-20T13:40:04.776Z" }, - { url = "https://files.pythonhosted.org/packages/af/e9/8259c4699227e5ee71d5d15a297f1e9469b99a980a1fd3140ecf68e98c4d/astropy-7.1.0-cp313-cp313-win32.whl", hash = "sha256:a77d07a104ec24ed7f8a45de3935f46071551aa2eb6d6b62efebdc888f65b6d6", size = 6150536, upload-time = "2025-05-20T13:40:06.638Z" }, - { url = "https://files.pythonhosted.org/packages/0b/dd/d9c55247172f7156696d85c9146b64b41c30405bf86b775a731bed4d52f8/astropy-7.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e317f34e33a8f5517bc9fc6fbc005f42730d3be7d2820ef41e0468bcb796843", size = 6278127, upload-time = "2025-05-20T13:40:08.643Z" }, + { url = "https://files.pythonhosted.org/packages/b4/6d/6330a844bad8dfc4875e0f2fa1db1fee87837ba9805aa8a8d048c071363a/astropy-7.2.0-cp311-abi3-macosx_10_9_x86_64.whl", hash = "sha256:efac04df4cc488efe630c2fff1992d6516dfb16a06e197fb68bc9e8e3b85def1", size = 6442332, upload-time = "2025-11-25T22:36:23.6Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ba/3418133ba144dfcd1530bca5a6b695f4cdd21a8abaaa2ac4e5450d11b028/astropy-7.2.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:52e9a7d9c86b21f1af911a2930cd0c4a275fb302d455c89e11eedaffef6f2ad0", size = 6413656, upload-time = "2025-11-25T22:36:26.548Z" }, + { url = "https://files.pythonhosted.org/packages/be/ba/05e43b5a7d738316a097fa78524d3eaaff5986294b4a052d4adb3c45e7c0/astropy-7.2.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97c370421b9bb13d4c762c7af06d172bad7c01bd5bcf88314f6913c3c235b770", size = 9758867, upload-time = "2025-11-25T22:36:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1c/f06ad85180e7dd9855aa5ede901bfc2be858d7bee17d4e978a14c0ecec14/astropy-7.2.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f39ce2c80211fbceb005d377a5478cd0d66c42aa1498d252f2239fe5a025c24", size = 9789007, upload-time = "2025-11-25T22:36:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fb/e4d35194a5009d7a73333079481a4ef1380a255d67b9c1db578151a5fb50/astropy-7.2.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ad4d71db994d45f046a1a5449000cf0f88ab6367cb67658500654a0586d6ab19", size = 9748547, upload-time = "2025-11-25T22:36:33.154Z" }, + { url = "https://files.pythonhosted.org/packages/36/ea/f990730978ae0a7a34705f885d2f3806928c5f0bc22eefd6a1a23539cc32/astropy-7.2.0-cp311-abi3-win32.whl", hash = "sha256:95161f26602433176483e8bde8ab1a8ca09148f5b4bf5190569a26d381091598", size = 6237228, upload-time = "2025-11-25T22:36:35.236Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bc/f4378f586dd63902c37d16f68f35f7d555b3b32e08ac6b1d633eb0a48805/astropy-7.2.0-cp311-abi3-win_amd64.whl", hash = "sha256:dc7c340ba1713e55c93071b32033f3153470a0f663a4d539c03a7c9b44020790", size = 6362868, upload-time = "2025-11-25T22:36:37.784Z" }, + { url = "https://files.pythonhosted.org/packages/77/79/b6d4bf01913cfd4ce0cd4c1be5916beccdb92b2970bab8c827984231eae6/astropy-7.2.0-cp311-abi3-win_arm64.whl", hash = "sha256:0c428735a3f15b05c2095bc6ccb5f98a64bc99fb7015866af19ff8492420ddaf", size = 6221756, upload-time = "2025-11-25T22:36:39.852Z" }, ] [[package]] name = "astropy-iers-data" -version = "0.2025.9.15.0.37.0" +version = "0.2026.4.27.1.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/97/247deecc933ca6a30ed137c19740d3f738df27fdc1de9c0a88cfaba00779/astropy_iers_data-0.2025.9.15.0.37.0.tar.gz", hash = "sha256:33e513dcfdc1b5a7c34e1cc674f4a050b29f93af5ac829e05d868a681d3091d8", size = 1908647, upload-time = "2025-09-15T00:37:46.311Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/e5/dc5474840c8b4ccf73db3475f88d9e40fcffa728f23e87707afa3460b30c/astropy_iers_data-0.2026.4.27.1.3.2.tar.gz", hash = "sha256:fc71b5b2e601afb1b8c4f22a35161c551d67469ec65502123591dae6a87d453b", size = 1931758, upload-time = "2026-04-27T01:03:45.375Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/8c/a556b71e4603d6156814da968b7cefa9bd882e59dc691168584418f4de77/astropy_iers_data-0.2025.9.15.0.37.0-py3-none-any.whl", hash = "sha256:4ea19813150f2d6dfb0257c5e96d14ae9eac6e5a80af27ec79e3aaef8aadfa93", size = 1964242, upload-time = "2025-09-15T00:37:44.298Z" }, + { url = "https://files.pythonhosted.org/packages/25/14/1c50311a2873e8c89f0b1d0eda65cf572b360917b14b70ba9d0678fc6acc/astropy_iers_data-0.2026.4.27.1.3.2-py3-none-any.whl", hash = "sha256:3c09006b1b7c369a4dd9ba7e395b04cbfedb41a9253013b3bf7e5b8ac53a7699", size = 1988568, upload-time = "2026-04-27T01:03:43.278Z" }, ] [[package]] @@ -144,11 +137,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.8.3" +version = "2026.4.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, ] [[package]] @@ -190,44 +183,75 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, - { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, - { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, - { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, - { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, - { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, - { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, - { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, - { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, - { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, - { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, - { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, - { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, - { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, - { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, - { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, - { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, - { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, - { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, - { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, - { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, - { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, - { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, - { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] @@ -241,46 +265,46 @@ wheels = [ [[package]] name = "cryptography" -version = "46.0.1" +version = "47.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/62/e3664e6ffd7743e1694b244dde70b43a394f6f7fbcacf7014a8ff5197c73/cryptography-46.0.1.tar.gz", hash = "sha256:ed570874e88f213437f5cf758f9ef26cbfc3f336d889b1e592ee11283bb8d1c7", size = 749198, upload-time = "2025-09-17T00:10:35.797Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/59/9ae689a25047e0601adfcb159ec4f83c0b4149fdb5c3030cc94cd218141d/cryptography-46.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ff483716be32690c14636e54a1f6e2e1b7bf8e22ca50b989f88fa1b2d287080", size = 4308182, upload-time = "2025-09-17T00:08:39.388Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ee/ca6cc9df7118f2fcd142c76b1da0f14340d77518c05b1ebfbbabca6b9e7d/cryptography-46.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9873bf7c1f2a6330bdfe8621e7ce64b725784f9f0c3a6a55c3047af5849f920e", size = 4572393, upload-time = "2025-09-17T00:08:41.663Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a3/0f5296f63815d8e985922b05c31f77ce44787b3127a67c0b7f70f115c45f/cryptography-46.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0dfb7c88d4462a0cfdd0d87a3c245a7bc3feb59de101f6ff88194f740f72eda6", size = 4308400, upload-time = "2025-09-17T00:08:43.559Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8c/74fcda3e4e01be1d32775d5b4dd841acaac3c1b8fa4d0774c7ac8d52463d/cryptography-46.0.1-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e22801b61613ebdebf7deb18b507919e107547a1d39a3b57f5f855032dd7cfb8", size = 4015786, upload-time = "2025-09-17T00:08:45.758Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b8/85d23287baeef273b0834481a3dd55bbed3a53587e3b8d9f0898235b8f91/cryptography-46.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:757af4f6341ce7a1e47c326ca2a81f41d236070217e5fbbad61bbfe299d55d28", size = 4982606, upload-time = "2025-09-17T00:08:47.602Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d3/de61ad5b52433b389afca0bc70f02a7a1f074651221f599ce368da0fe437/cryptography-46.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f7a24ea78de345cfa7f6a8d3bde8b242c7fac27f2bd78fa23474ca38dfaeeab9", size = 4604234, upload-time = "2025-09-17T00:08:49.879Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1f/dbd4d6570d84748439237a7478d124ee0134bf166ad129267b7ed8ea6d22/cryptography-46.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e8776dac9e660c22241b6587fae51a67b4b0147daa4d176b172c3ff768ad736", size = 4307669, upload-time = "2025-09-17T00:08:52.321Z" }, - { url = "https://files.pythonhosted.org/packages/ec/fd/ca0a14ce7f0bfe92fa727aacaf2217eb25eb7e4ed513b14d8e03b26e63ed/cryptography-46.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9f40642a140c0c8649987027867242b801486865277cbabc8c6059ddef16dc8b", size = 4947579, upload-time = "2025-09-17T00:08:54.697Z" }, - { url = "https://files.pythonhosted.org/packages/89/6b/09c30543bb93401f6f88fce556b3bdbb21e55ae14912c04b7bf355f5f96c/cryptography-46.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:449ef2b321bec7d97ef2c944173275ebdab78f3abdd005400cc409e27cd159ab", size = 4603669, upload-time = "2025-09-17T00:08:57.16Z" }, - { url = "https://files.pythonhosted.org/packages/23/9a/38cb01cb09ce0adceda9fc627c9cf98eb890fc8d50cacbe79b011df20f8a/cryptography-46.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2dd339ba3345b908fa3141ddba4025568fa6fd398eabce3ef72a29ac2d73ad75", size = 4435828, upload-time = "2025-09-17T00:08:59.606Z" }, - { url = "https://files.pythonhosted.org/packages/0f/53/435b5c36a78d06ae0bef96d666209b0ecd8f8181bfe4dda46536705df59e/cryptography-46.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7411c910fb2a412053cf33cfad0153ee20d27e256c6c3f14d7d7d1d9fec59fd5", size = 4709553, upload-time = "2025-09-17T00:09:01.832Z" }, - { url = "https://files.pythonhosted.org/packages/26/34/0ff0bb2d2c79f25a2a63109f3b76b9108a906dd2a2eb5c1d460b9938adbb/cryptography-46.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9babb7818fdd71394e576cf26c5452df77a355eac1a27ddfa24096665a27f8fd", size = 4293515, upload-time = "2025-09-17T00:09:12.861Z" }, - { url = "https://files.pythonhosted.org/packages/df/b7/d4f848aee24ecd1be01db6c42c4a270069a4f02a105d9c57e143daf6cf0f/cryptography-46.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9f2c4cc63be3ef43c0221861177cee5d14b505cd4d4599a89e2cd273c4d3542a", size = 4545619, upload-time = "2025-09-17T00:09:15.397Z" }, - { url = "https://files.pythonhosted.org/packages/44/a5/42fedefc754fd1901e2d95a69815ea4ec8a9eed31f4c4361fcab80288661/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:41c281a74df173876da1dc9a9b6953d387f06e3d3ed9284e3baae3ab3f40883a", size = 4299160, upload-time = "2025-09-17T00:09:17.155Z" }, - { url = "https://files.pythonhosted.org/packages/86/a1/cd21174f56e769c831fbbd6399a1b7519b0ff6280acec1b826d7b072640c/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0a17377fa52563d730248ba1f68185461fff36e8bc75d8787a7dd2e20a802b7a", size = 3994491, upload-time = "2025-09-17T00:09:18.971Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2f/a8cbfa1c029987ddc746fd966711d4fa71efc891d37fbe9f030fe5ab4eec/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:0d1922d9280e08cde90b518a10cd66831f632960a8d08cb3418922d83fce6f12", size = 4960157, upload-time = "2025-09-17T00:09:20.923Z" }, - { url = "https://files.pythonhosted.org/packages/67/ae/63a84e6789e0d5a2502edf06b552bcb0fa9ff16147265d5c44a211942abe/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:af84e8e99f1a82cea149e253014ea9dc89f75b82c87bb6c7242203186f465129", size = 4577263, upload-time = "2025-09-17T00:09:23.356Z" }, - { url = "https://files.pythonhosted.org/packages/ef/8f/1b9fa8e92bd9cbcb3b7e1e593a5232f2c1e6f9bd72b919c1a6b37d315f92/cryptography-46.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ef648d2c690703501714588b2ba640facd50fd16548133b11b2859e8655a69da", size = 4298703, upload-time = "2025-09-17T00:09:25.566Z" }, - { url = "https://files.pythonhosted.org/packages/c3/af/bb95db070e73fea3fae31d8a69ac1463d89d1c084220f549b00dd01094a8/cryptography-46.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:e94eb5fa32a8a9f9bf991f424f002913e3dd7c699ef552db9b14ba6a76a6313b", size = 4926363, upload-time = "2025-09-17T00:09:27.451Z" }, - { url = "https://files.pythonhosted.org/packages/f5/3b/d8fb17ffeb3a83157a1cc0aa5c60691d062aceecba09c2e5e77ebfc1870c/cryptography-46.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:534b96c0831855e29fc3b069b085fd185aa5353033631a585d5cd4dd5d40d657", size = 4576958, upload-time = "2025-09-17T00:09:29.924Z" }, - { url = "https://files.pythonhosted.org/packages/d9/46/86bc3a05c10c8aa88c8ae7e953a8b4e407c57823ed201dbcba55c4d655f4/cryptography-46.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9b55038b5c6c47559aa33626d8ecd092f354e23de3c6975e4bb205df128a2a0", size = 4422507, upload-time = "2025-09-17T00:09:32.222Z" }, - { url = "https://files.pythonhosted.org/packages/a8/4e/387e5a21dfd2b4198e74968a541cfd6128f66f8ec94ed971776e15091ac3/cryptography-46.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ec13b7105117dbc9afd023300fb9954d72ca855c274fe563e72428ece10191c0", size = 4683964, upload-time = "2025-09-17T00:09:34.118Z" }, - { url = "https://files.pythonhosted.org/packages/56/3e/13ce6eab9ad6eba1b15a7bd476f005a4c1b3f299f4c2f32b22408b0edccf/cryptography-46.0.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9ed64e5083fa806709e74fc5ea067dfef9090e5b7a2320a49be3c9df3583a2d8", size = 4301110, upload-time = "2025-09-17T00:09:45.614Z" }, - { url = "https://files.pythonhosted.org/packages/a2/67/65dc233c1ddd688073cf7b136b06ff4b84bf517ba5529607c9d79720fc67/cryptography-46.0.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:341fb7a26bc9d6093c1b124b9f13acc283d2d51da440b98b55ab3f79f2522ead", size = 4562369, upload-time = "2025-09-17T00:09:47.601Z" }, - { url = "https://files.pythonhosted.org/packages/17/db/d64ae4c6f4e98c3dac5bf35dd4d103f4c7c345703e43560113e5e8e31b2b/cryptography-46.0.1-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6ef1488967e729948d424d09c94753d0167ce59afba8d0f6c07a22b629c557b2", size = 4302126, upload-time = "2025-09-17T00:09:49.335Z" }, - { url = "https://files.pythonhosted.org/packages/3d/19/5f1eea17d4805ebdc2e685b7b02800c4f63f3dd46cfa8d4c18373fea46c8/cryptography-46.0.1-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7823bc7cdf0b747ecfb096d004cc41573c2f5c7e3a29861603a2871b43d3ef32", size = 4009431, upload-time = "2025-09-17T00:09:51.239Z" }, - { url = "https://files.pythonhosted.org/packages/81/b5/229ba6088fe7abccbfe4c5edb96c7a5ad547fac5fdd0d40aa6ea540b2985/cryptography-46.0.1-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:f736ab8036796f5a119ff8211deda416f8c15ce03776db704a7a4e17381cb2ef", size = 4980739, upload-time = "2025-09-17T00:09:54.181Z" }, - { url = "https://files.pythonhosted.org/packages/3a/9c/50aa38907b201e74bc43c572f9603fa82b58e831bd13c245613a23cff736/cryptography-46.0.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e46710a240a41d594953012213ea8ca398cd2448fbc5d0f1be8160b5511104a0", size = 4592289, upload-time = "2025-09-17T00:09:56.731Z" }, - { url = "https://files.pythonhosted.org/packages/5a/33/229858f8a5bb22f82468bb285e9f4c44a31978d5f5830bb4ea1cf8a4e454/cryptography-46.0.1-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:84ef1f145de5aee82ea2447224dc23f065ff4cc5791bb3b506615957a6ba8128", size = 4301815, upload-time = "2025-09-17T00:09:58.548Z" }, - { url = "https://files.pythonhosted.org/packages/52/cb/b76b2c87fbd6ed4a231884bea3ce073406ba8e2dae9defad910d33cbf408/cryptography-46.0.1-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9394c7d5a7565ac5f7d9ba38b2617448eba384d7b107b262d63890079fad77ca", size = 4943251, upload-time = "2025-09-17T00:10:00.475Z" }, - { url = "https://files.pythonhosted.org/packages/94/0f/f66125ecf88e4cb5b8017ff43f3a87ede2d064cb54a1c5893f9da9d65093/cryptography-46.0.1-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ed957044e368ed295257ae3d212b95456bd9756df490e1ac4538857f67531fcc", size = 4591247, upload-time = "2025-09-17T00:10:02.874Z" }, - { url = "https://files.pythonhosted.org/packages/f6/22/9f3134ae436b63b463cfdf0ff506a0570da6873adb4bf8c19b8a5b4bac64/cryptography-46.0.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f7de12fa0eee6234de9a9ce0ffcfa6ce97361db7a50b09b65c63ac58e5f22fc7", size = 4428534, upload-time = "2025-09-17T00:10:04.994Z" }, - { url = "https://files.pythonhosted.org/packages/89/39/e6042bcb2638650b0005c752c38ea830cbfbcbb1830e4d64d530000aa8dc/cryptography-46.0.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7fab1187b6c6b2f11a326f33b036f7168f5b996aedd0c059f9738915e4e8f53a", size = 4699541, upload-time = "2025-09-17T00:10:06.925Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/c6/2733531243fba725f58611b918056b277692f1033373dcc8bd01af1c05d4/cryptography-47.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973", size = 4644617, upload-time = "2026-04-24T19:53:06.909Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/b27be1a670a9b87f855d211cf0e1174a5d721216b7616bd52d8581d912ed/cryptography-47.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8", size = 4668186, upload-time = "2026-04-24T19:53:09.053Z" }, + { url = "https://files.pythonhosted.org/packages/81/b9/8443cfe5d17d482d348cee7048acf502bb89a51b6382f06240fd290d4ca3/cryptography-47.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b", size = 4651244, upload-time = "2026-04-24T19:53:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5e/13ed0cdd0eb88ba159d6dd5ebfece8cb901dbcf1ae5ac4072e28b55d3153/cryptography-47.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:34b4358b925a5ea3e14384ca781a2c0ef7ac219b57bb9eacc4457078e2b19f92", size = 5252906, upload-time = "2026-04-24T19:53:13.532Z" }, + { url = "https://files.pythonhosted.org/packages/64/16/ed058e1df0f33d440217cd120d41d5dda9dd215a80b8187f68483185af82/cryptography-47.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7", size = 4701842, upload-time = "2026-04-24T19:53:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3d30986b30fdbd9e969abbdf8ba00ed0618615144341faeb57f395a084fe/cryptography-47.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:1e47422b5557bb82d3fff997e8d92cff4e28b9789576984f08c248d2b3535d93", size = 4289313, upload-time = "2026-04-24T19:53:17.755Z" }, + { url = "https://files.pythonhosted.org/packages/df/fd/32db38e3ad0cb331f0691cb4c7a8a6f176f679124dee746b3af6633db4d9/cryptography-47.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6f29f36582e6151d9686235e586dd35bb67491f024767d10b842e520dc6a07ac", size = 4650964, upload-time = "2026-04-24T19:53:20.062Z" }, + { url = "https://files.pythonhosted.org/packages/86/53/5395d944dfd48cb1f67917f533c609c34347185ef15eb4308024c876f274/cryptography-47.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a9b761f012a943b7de0e828843c5688d0de94a0578d44d6c85a1bae32f87791f", size = 5207817, upload-time = "2026-04-24T19:53:22.498Z" }, + { url = "https://files.pythonhosted.org/packages/34/4f/e5711b28e1901f7d480a2b1b688b645aa4c77c73f10731ed17e7f7db3f0d/cryptography-47.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8", size = 4701544, upload-time = "2026-04-24T19:53:24.356Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/c8ddc25de3010fc8da447648f5a092c40e7a8fadf01dd6d255d9c0b9373d/cryptography-47.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318", size = 4783536, upload-time = "2026-04-24T19:53:26.665Z" }, + { url = "https://files.pythonhosted.org/packages/66/b6/d4a68f4ea999c6d89e8498579cba1c5fcba4276284de7773b17e4fa69293/cryptography-47.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001", size = 4926106, upload-time = "2026-04-24T19:53:28.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/55/c18f75724544872f234678fdedc871391722cb34a2aee19faa9f63100bb2/cryptography-47.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7", size = 4631180, upload-time = "2026-04-24T19:53:37.517Z" }, + { url = "https://files.pythonhosted.org/packages/ee/65/31a5cc0eaca99cec5bafffe155d407115d96136bb161e8b49e0ef73f09a7/cryptography-47.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1", size = 4653529, upload-time = "2026-04-24T19:53:39.775Z" }, + { url = "https://files.pythonhosted.org/packages/e5/bc/641c0519a495f3bfd0421b48d7cd325c4336578523ccd76ea322b6c29c7a/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c", size = 4638570, upload-time = "2026-04-24T19:53:42.129Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f2/300327b0a47f6dc94dd8b71b57052aefe178bb51745073d73d80604f11ab/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3fb8fa48075fad7193f2e5496135c6a76ac4b2aa5a38433df0a539296b377829", size = 5238019, upload-time = "2026-04-24T19:53:44.577Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/5b5cf994391d4bf9d9c7efd4c66aabe4d95227256627f8fea6cff7dfadbd/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11438c7518132d95f354fa01a4aa2f806d172a061a7bed18cf18cbdacdb204d7", size = 4686832, upload-time = "2026-04-24T19:53:47.015Z" }, + { url = "https://files.pythonhosted.org/packages/dc/2c/ae950e28fd6475c852fc21a44db3e6b5bcc1261d1e370f2b6e42fa800fef/cryptography-47.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8c1a736bbb3288005796c3f7ccb9453360d7fed483b13b9f468aea5171432923", size = 4269301, upload-time = "2026-04-24T19:53:48.97Z" }, + { url = "https://files.pythonhosted.org/packages/67/fb/6a39782e150ffe5cc1b0018cb6ddc48bf7ca62b498d7539ffc8a758e977d/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:f1557695e5c2b86e204f6ce9470497848634100787935ab7adc5397c54abd7ab", size = 4638110, upload-time = "2026-04-24T19:53:51.011Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d7/0b3c71090a76e5c203164a47688b697635ece006dcd2499ab3a4dbd3f0bd/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:f9a034b642b960767fb343766ae5ba6ad653f2e890ddd82955aef288ffea8736", size = 5194988, upload-time = "2026-04-24T19:53:52.962Z" }, + { url = "https://files.pythonhosted.org/packages/63/33/63a961498a9df51721ab578c5a2622661411fc520e00bd83b0cc64eb20c4/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7", size = 4686563, upload-time = "2026-04-24T19:53:55.274Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bf/5ee5b145248f92250de86145d1c1d6edebbd57a7fe7caa4dedb5d4cf06a1/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52", size = 4770094, upload-time = "2026-04-24T19:53:57.753Z" }, + { url = "https://files.pythonhosted.org/packages/92/43/21d220b2da5d517773894dacdcdb5c682c28d3fffce65548cb06e87d5501/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd", size = 4913811, upload-time = "2026-04-24T19:54:00.236Z" }, + { url = "https://files.pythonhosted.org/packages/01/64/d7b1e54fdb69f22d24a64bb3e88dc718b31c7fb10ef0b9691a3cf7eeea6e/cryptography-47.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27", size = 4635767, upload-time = "2026-04-24T19:54:08.519Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7b/cca826391fb2a94efdcdfe4631eb69306ee1cff0b22f664a412c90713877/cryptography-47.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10", size = 4654350, upload-time = "2026-04-24T19:54:10.795Z" }, + { url = "https://files.pythonhosted.org/packages/4c/65/4b57bcc823f42a991627c51c2f68c9fd6eb1393c1756aac876cba2accae2/cryptography-47.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b", size = 4643394, upload-time = "2026-04-24T19:54:13.275Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/2c5fbeea70adbbca2bbae865e1d605d6a4a7f8dbd9d33eaf69645087f06c/cryptography-47.0.0-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9af828c0d5a65c70ec729cd7495a4bf1a67ecb66417b8f02ff125ab8a6326a74", size = 5225777, upload-time = "2026-04-24T19:54:15.18Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b8/ac57107ef32749d2b244e36069bb688792a363aaaa3acc9e3cf84c130315/cryptography-47.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:256d07c78a04d6b276f5df935a9923275f53bd1522f214447fdf365494e2d515", size = 4688771, upload-time = "2026-04-24T19:54:17.835Z" }, + { url = "https://files.pythonhosted.org/packages/56/fc/9f1de22ff8be99d991f240a46863c52d475404c408886c5a38d2b5c3bb26/cryptography-47.0.0-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:5d0e362ff51041b0c0d219cc7d6924d7b8996f57ce5712bdcef71eb3c65a59cc", size = 4270753, upload-time = "2026-04-24T19:54:19.963Z" }, + { url = "https://files.pythonhosted.org/packages/00/68/d70c852797aa68e8e48d12e5a87170c43f67bb4a59403627259dd57d15de/cryptography-47.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1581aef4219f7ca2849d0250edaa3866212fb74bf5667284f46aa92f9e65c1ca", size = 4642911, upload-time = "2026-04-24T19:54:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/a5/51/661cbee74f594c5d97ff82d34f10d5551c085ca4668645f4606ebd22bd5d/cryptography-47.0.0-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a49a3eb5341b9503fa3000a9a0db033161db90d47285291f53c2a9d2cd1b7f76", size = 5181411, upload-time = "2026-04-24T19:54:24.376Z" }, + { url = "https://files.pythonhosted.org/packages/94/87/f2b6c374a82cf076cfa1416992ac8e8ec94d79facc37aec87c1a5cb72352/cryptography-47.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe", size = 4688262, upload-time = "2026-04-24T19:54:26.946Z" }, + { url = "https://files.pythonhosted.org/packages/14/e2/8b7462f4acf21ec509616f0245018bb197194ab0b65c2ea21a0bdd53c0eb/cryptography-47.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31", size = 4775506, upload-time = "2026-04-24T19:54:28.926Z" }, + { url = "https://files.pythonhosted.org/packages/70/75/158e494e4c08dc05e039da5bb48553826bd26c23930cf8d3cd5f21fa8921/cryptography-47.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7", size = 4912060, upload-time = "2026-04-24T19:54:30.869Z" }, ] [[package]] @@ -331,20 +355,20 @@ wheels = [ [[package]] name = "idna" -version = "3.10" +version = "3.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, ] [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] @@ -361,23 +385,23 @@ wheels = [ [[package]] name = "jaraco-context" -version = "6.0.1" +version = "6.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912, upload-time = "2024-08-20T03:39:27.358Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825, upload-time = "2024-08-20T03:39:25.966Z" }, + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, ] [[package]] name = "jaraco-functools" -version = "4.3.0" +version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/ed/1aa2d585304ec07262e1a83a9889880701079dde796ac7b1d1826f40c63d/jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294", size = 19755, upload-time = "2025-08-18T20:05:09.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", size = 10408, upload-time = "2025-08-18T20:05:08.69Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, ] [[package]] @@ -403,7 +427,7 @@ wheels = [ [[package]] name = "keyring" -version = "25.6.0" +version = "25.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jaraco-classes" }, @@ -413,71 +437,89 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750, upload-time = "2024-12-25T15:26:45.782Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, ] [[package]] name = "lxml" -version = "6.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8f/bd/f9d01fd4132d81c6f43ab01983caea69ec9614b913c290a26738431a015d/lxml-6.0.1.tar.gz", hash = "sha256:2b3a882ebf27dd026df3801a87cf49ff791336e0f94b0fad195db77e01240690", size = 4070214, upload-time = "2025-08-22T10:37:53.525Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/a9/82b244c8198fcdf709532e39a1751943a36b3e800b420adc739d751e0299/lxml-6.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c03ac546adaabbe0b8e4a15d9ad815a281afc8d36249c246aecf1aaad7d6f200", size = 8422788, upload-time = "2025-08-22T10:32:56.612Z" }, - { url = "https://files.pythonhosted.org/packages/c9/8d/1ed2bc20281b0e7ed3e6c12b0a16e64ae2065d99be075be119ba88486e6d/lxml-6.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33b862c7e3bbeb4ba2c96f3a039f925c640eeba9087a4dc7a572ec0f19d89392", size = 4593547, upload-time = "2025-08-22T10:32:59.016Z" }, - { url = "https://files.pythonhosted.org/packages/76/53/d7fd3af95b72a3493bf7fbe842a01e339d8f41567805cecfecd5c71aa5ee/lxml-6.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a3ec1373f7d3f519de595032d4dcafae396c29407cfd5073f42d267ba32440d", size = 4948101, upload-time = "2025-08-22T10:33:00.765Z" }, - { url = "https://files.pythonhosted.org/packages/9d/51/4e57cba4d55273c400fb63aefa2f0d08d15eac021432571a7eeefee67bed/lxml-6.0.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03b12214fb1608f4cffa181ec3d046c72f7e77c345d06222144744c122ded870", size = 5108090, upload-time = "2025-08-22T10:33:03.108Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6e/5f290bc26fcc642bc32942e903e833472271614e24d64ad28aaec09d5dae/lxml-6.0.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:207ae0d5f0f03b30f95e649a6fa22aa73f5825667fee9c7ec6854d30e19f2ed8", size = 5021791, upload-time = "2025-08-22T10:33:06.972Z" }, - { url = "https://files.pythonhosted.org/packages/13/d4/2e7551a86992ece4f9a0f6eebd4fb7e312d30f1e372760e2109e721d4ce6/lxml-6.0.1-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:32297b09ed4b17f7b3f448de87a92fb31bb8747496623483788e9f27c98c0f00", size = 5358861, upload-time = "2025-08-22T10:33:08.967Z" }, - { url = "https://files.pythonhosted.org/packages/8a/5f/cb49d727fc388bf5fd37247209bab0da11697ddc5e976ccac4826599939e/lxml-6.0.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e18224ea241b657a157c85e9cac82c2b113ec90876e01e1f127312006233756", size = 5652569, upload-time = "2025-08-22T10:33:10.815Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b8/66c1ef8c87ad0f958b0a23998851e610607c74849e75e83955d5641272e6/lxml-6.0.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a07a994d3c46cd4020c1ea566345cf6815af205b1e948213a4f0f1d392182072", size = 5252262, upload-time = "2025-08-22T10:33:12.673Z" }, - { url = "https://files.pythonhosted.org/packages/1a/ef/131d3d6b9590e64fdbb932fbc576b81fcc686289da19c7cb796257310e82/lxml-6.0.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:2287fadaa12418a813b05095485c286c47ea58155930cfbd98c590d25770e225", size = 4710309, upload-time = "2025-08-22T10:33:14.952Z" }, - { url = "https://files.pythonhosted.org/packages/bc/3f/07f48ae422dce44902309aa7ed386c35310929dc592439c403ec16ef9137/lxml-6.0.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b4e597efca032ed99f418bd21314745522ab9fa95af33370dcee5533f7f70136", size = 5265786, upload-time = "2025-08-22T10:33:16.721Z" }, - { url = "https://files.pythonhosted.org/packages/11/c7/125315d7b14ab20d9155e8316f7d287a4956098f787c22d47560b74886c4/lxml-6.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9696d491f156226decdd95d9651c6786d43701e49f32bf23715c975539aa2b3b", size = 5062272, upload-time = "2025-08-22T10:33:18.478Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c3/51143c3a5fc5168a7c3ee626418468ff20d30f5a59597e7b156c1e61fba8/lxml-6.0.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e4e3cd3585f3c6f87cdea44cda68e692cc42a012f0131d25957ba4ce755241a7", size = 4786955, upload-time = "2025-08-22T10:33:20.34Z" }, - { url = "https://files.pythonhosted.org/packages/11/86/73102370a420ec4529647b31c4a8ce8c740c77af3a5fae7a7643212d6f6e/lxml-6.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:45cbc92f9d22c28cd3b97f8d07fcefa42e569fbd587dfdac76852b16a4924277", size = 5673557, upload-time = "2025-08-22T10:33:22.282Z" }, - { url = "https://files.pythonhosted.org/packages/d7/2d/aad90afaec51029aef26ef773b8fd74a9e8706e5e2f46a57acd11a421c02/lxml-6.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f8c9bcfd2e12299a442fba94459adf0b0d001dbc68f1594439bfa10ad1ecb74b", size = 5254211, upload-time = "2025-08-22T10:33:24.15Z" }, - { url = "https://files.pythonhosted.org/packages/63/01/c9e42c8c2d8b41f4bdefa42ab05448852e439045f112903dd901b8fbea4d/lxml-6.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1e9dc2b9f1586e7cd77753eae81f8d76220eed9b768f337dc83a3f675f2f0cf9", size = 5275817, upload-time = "2025-08-22T10:33:26.007Z" }, - { url = "https://files.pythonhosted.org/packages/bc/1f/962ea2696759abe331c3b0e838bb17e92224f39c638c2068bf0d8345e913/lxml-6.0.1-cp312-cp312-win32.whl", hash = "sha256:987ad5c3941c64031f59c226167f55a04d1272e76b241bfafc968bdb778e07fb", size = 3610889, upload-time = "2025-08-22T10:33:28.169Z" }, - { url = "https://files.pythonhosted.org/packages/41/e2/22c86a990b51b44442b75c43ecb2f77b8daba8c4ba63696921966eac7022/lxml-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:abb05a45394fd76bf4a60c1b7bec0e6d4e8dfc569fc0e0b1f634cd983a006ddc", size = 4010925, upload-time = "2025-08-22T10:33:29.874Z" }, - { url = "https://files.pythonhosted.org/packages/b2/21/dc0c73325e5eb94ef9c9d60dbb5dcdcb2e7114901ea9509735614a74e75a/lxml-6.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:c4be29bce35020d8579d60aa0a4e95effd66fcfce31c46ffddf7e5422f73a299", size = 3671922, upload-time = "2025-08-22T10:33:31.535Z" }, - { url = "https://files.pythonhosted.org/packages/43/c4/cd757eeec4548e6652eff50b944079d18ce5f8182d2b2cf514e125e8fbcb/lxml-6.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:485eda5d81bb7358db96a83546949c5fe7474bec6c68ef3fa1fb61a584b00eea", size = 8405139, upload-time = "2025-08-22T10:33:34.09Z" }, - { url = "https://files.pythonhosted.org/packages/ff/99/0290bb86a7403893f5e9658490c705fcea103b9191f2039752b071b4ef07/lxml-6.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d12160adea318ce3d118f0b4fbdff7d1225c75fb7749429541b4d217b85c3f76", size = 4585954, upload-time = "2025-08-22T10:33:36.294Z" }, - { url = "https://files.pythonhosted.org/packages/88/a7/4bb54dd1e626342a0f7df6ec6ca44fdd5d0e100ace53acc00e9a689ead04/lxml-6.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48c8d335d8ab72f9265e7ba598ae5105a8272437403f4032107dbcb96d3f0b29", size = 4944052, upload-time = "2025-08-22T10:33:38.19Z" }, - { url = "https://files.pythonhosted.org/packages/71/8d/20f51cd07a7cbef6214675a8a5c62b2559a36d9303fe511645108887c458/lxml-6.0.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:405e7cf9dbdbb52722c231e0f1257214202dfa192327fab3de45fd62e0554082", size = 5098885, upload-time = "2025-08-22T10:33:40.035Z" }, - { url = "https://files.pythonhosted.org/packages/5a/63/efceeee7245d45f97d548e48132258a36244d3c13c6e3ddbd04db95ff496/lxml-6.0.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:299a790d403335a6a057ade46f92612ebab87b223e4e8c5308059f2dc36f45ed", size = 5017542, upload-time = "2025-08-22T10:33:41.896Z" }, - { url = "https://files.pythonhosted.org/packages/57/5d/92cb3d3499f5caba17f7933e6be3b6c7de767b715081863337ced42eb5f2/lxml-6.0.1-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:48da704672f6f9c461e9a73250440c647638cc6ff9567ead4c3b1f189a604ee8", size = 5347303, upload-time = "2025-08-22T10:33:43.868Z" }, - { url = "https://files.pythonhosted.org/packages/69/f8/606fa16a05d7ef5e916c6481c634f40870db605caffed9d08b1a4fb6b989/lxml-6.0.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:21e364e1bb731489e3f4d51db416f991a5d5da5d88184728d80ecfb0904b1d68", size = 5641055, upload-time = "2025-08-22T10:33:45.784Z" }, - { url = "https://files.pythonhosted.org/packages/b3/01/15d5fc74ebb49eac4e5df031fbc50713dcc081f4e0068ed963a510b7d457/lxml-6.0.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bce45a2c32032afddbd84ed8ab092130649acb935536ef7a9559636ce7ffd4a", size = 5242719, upload-time = "2025-08-22T10:33:48.089Z" }, - { url = "https://files.pythonhosted.org/packages/42/a5/1b85e2aaaf8deaa67e04c33bddb41f8e73d07a077bf9db677cec7128bfb4/lxml-6.0.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:fa164387ff20ab0e575fa909b11b92ff1481e6876835014e70280769920c4433", size = 4717310, upload-time = "2025-08-22T10:33:49.852Z" }, - { url = "https://files.pythonhosted.org/packages/42/23/f3bb1292f55a725814317172eeb296615db3becac8f1a059b53c51fc1da8/lxml-6.0.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7587ac5e000e1594e62278422c5783b34a82b22f27688b1074d71376424b73e8", size = 5254024, upload-time = "2025-08-22T10:33:52.22Z" }, - { url = "https://files.pythonhosted.org/packages/b4/be/4d768f581ccd0386d424bac615d9002d805df7cc8482ae07d529f60a3c1e/lxml-6.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:57478424ac4c9170eabf540237125e8d30fad1940648924c058e7bc9fb9cf6dd", size = 5055335, upload-time = "2025-08-22T10:33:54.041Z" }, - { url = "https://files.pythonhosted.org/packages/40/07/ed61d1a3e77d1a9f856c4fab15ee5c09a2853fb7af13b866bb469a3a6d42/lxml-6.0.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:09c74afc7786c10dd6afaa0be2e4805866beadc18f1d843cf517a7851151b499", size = 4784864, upload-time = "2025-08-22T10:33:56.382Z" }, - { url = "https://files.pythonhosted.org/packages/01/37/77e7971212e5c38a55431744f79dff27fd751771775165caea096d055ca4/lxml-6.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7fd70681aeed83b196482d42a9b0dc5b13bab55668d09ad75ed26dff3be5a2f5", size = 5657173, upload-time = "2025-08-22T10:33:58.698Z" }, - { url = "https://files.pythonhosted.org/packages/32/a3/e98806d483941cd9061cc838b1169626acef7b2807261fbe5e382fcef881/lxml-6.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:10a72e456319b030b3dd900df6b1f19d89adf06ebb688821636dc406788cf6ac", size = 5245896, upload-time = "2025-08-22T10:34:00.586Z" }, - { url = "https://files.pythonhosted.org/packages/07/de/9bb5a05e42e8623bf06b4638931ea8c8f5eb5a020fe31703abdbd2e83547/lxml-6.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b0fa45fb5f55111ce75b56c703843b36baaf65908f8b8d2fbbc0e249dbc127ed", size = 5267417, upload-time = "2025-08-22T10:34:02.719Z" }, - { url = "https://files.pythonhosted.org/packages/f2/43/c1cb2a7c67226266c463ef8a53b82d42607228beb763b5fbf4867e88a21f/lxml-6.0.1-cp313-cp313-win32.whl", hash = "sha256:01dab65641201e00c69338c9c2b8a0f2f484b6b3a22d10779bb417599fae32b5", size = 3610051, upload-time = "2025-08-22T10:34:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/34/96/6a6c3b8aa480639c1a0b9b6faf2a63fb73ab79ffcd2a91cf28745faa22de/lxml-6.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:bdf8f7c8502552d7bff9e4c98971910a0a59f60f88b5048f608d0a1a75e94d1c", size = 4009325, upload-time = "2025-08-22T10:34:06.24Z" }, - { url = "https://files.pythonhosted.org/packages/8c/66/622e8515121e1fd773e3738dae71b8df14b12006d9fb554ce90886689fd0/lxml-6.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a6aeca75959426b9fd8d4782c28723ba224fe07cfa9f26a141004210528dcbe2", size = 3670443, upload-time = "2025-08-22T10:34:07.974Z" }, - { url = "https://files.pythonhosted.org/packages/38/e3/b7eb612ce07abe766918a7e581ec6a0e5212352194001fd287c3ace945f0/lxml-6.0.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:29b0e849ec7030e3ecb6112564c9f7ad6881e3b2375dd4a0c486c5c1f3a33859", size = 8426160, upload-time = "2025-08-22T10:34:10.154Z" }, - { url = "https://files.pythonhosted.org/packages/35/8f/ab3639a33595cf284fe733c6526da2ca3afbc5fd7f244ae67f3303cec654/lxml-6.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:02a0f7e629f73cc0be598c8b0611bf28ec3b948c549578a26111b01307fd4051", size = 4589288, upload-time = "2025-08-22T10:34:12.972Z" }, - { url = "https://files.pythonhosted.org/packages/2c/65/819d54f2e94d5c4458c1db8c1ccac9d05230b27c1038937d3d788eb406f9/lxml-6.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:beab5e54de016e730875f612ba51e54c331e2fa6dc78ecf9a5415fc90d619348", size = 4964523, upload-time = "2025-08-22T10:34:15.474Z" }, - { url = "https://files.pythonhosted.org/packages/5b/4a/d4a74ce942e60025cdaa883c5a4478921a99ce8607fc3130f1e349a83b28/lxml-6.0.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a08aefecd19ecc4ebf053c27789dd92c87821df2583a4337131cf181a1dffa", size = 5101108, upload-time = "2025-08-22T10:34:17.348Z" }, - { url = "https://files.pythonhosted.org/packages/cb/48/67f15461884074edd58af17b1827b983644d1fae83b3d909e9045a08b61e/lxml-6.0.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36c8fa7e177649470bc3dcf7eae6bee1e4984aaee496b9ccbf30e97ac4127fa2", size = 5053498, upload-time = "2025-08-22T10:34:19.232Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d4/ec1bf1614828a5492f4af0b6a9ee2eb3e92440aea3ac4fa158e5228b772b/lxml-6.0.1-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:5d08e0f1af6916267bb7eff21c09fa105620f07712424aaae09e8cb5dd4164d1", size = 5351057, upload-time = "2025-08-22T10:34:21.143Z" }, - { url = "https://files.pythonhosted.org/packages/65/2b/c85929dacac08821f2100cea3eb258ce5c8804a4e32b774f50ebd7592850/lxml-6.0.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9705cdfc05142f8c38c97a61bd3a29581ceceb973a014e302ee4a73cc6632476", size = 5671579, upload-time = "2025-08-22T10:34:23.528Z" }, - { url = "https://files.pythonhosted.org/packages/d0/36/cf544d75c269b9aad16752fd9f02d8e171c5a493ca225cb46bb7ba72868c/lxml-6.0.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74555e2da7c1636e30bff4e6e38d862a634cf020ffa591f1f63da96bf8b34772", size = 5250403, upload-time = "2025-08-22T10:34:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e8/83dbc946ee598fd75fdeae6151a725ddeaab39bb321354a9468d4c9f44f3/lxml-6.0.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:e38b5f94c5a2a5dadaddd50084098dfd005e5a2a56cd200aaf5e0a20e8941782", size = 4696712, upload-time = "2025-08-22T10:34:27.753Z" }, - { url = "https://files.pythonhosted.org/packages/f4/72/889c633b47c06205743ba935f4d1f5aa4eb7f0325d701ed2b0540df1b004/lxml-6.0.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a5ec101a92ddacb4791977acfc86c1afd624c032974bfb6a21269d1083c9bc49", size = 5268177, upload-time = "2025-08-22T10:34:29.804Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b6/f42a21a1428479b66ea0da7bd13e370436aecaff0cfe93270c7e165bd2a4/lxml-6.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5c17e70c82fd777df586c12114bbe56e4e6f823a971814fd40dec9c0de518772", size = 5094648, upload-time = "2025-08-22T10:34:31.703Z" }, - { url = "https://files.pythonhosted.org/packages/51/b0/5f8c1e8890e2ee1c2053c2eadd1cb0e4b79e2304e2912385f6ca666f48b1/lxml-6.0.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:45fdd0415a0c3d91640b5d7a650a8f37410966a2e9afebb35979d06166fd010e", size = 4745220, upload-time = "2025-08-22T10:34:33.595Z" }, - { url = "https://files.pythonhosted.org/packages/eb/f9/820b5125660dae489ca3a21a36d9da2e75dd6b5ffe922088f94bbff3b8a0/lxml-6.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d417eba28981e720a14fcb98f95e44e7a772fe25982e584db38e5d3b6ee02e79", size = 5692913, upload-time = "2025-08-22T10:34:35.482Z" }, - { url = "https://files.pythonhosted.org/packages/23/8e/a557fae9eec236618aecf9ff35fec18df41b6556d825f3ad6017d9f6e878/lxml-6.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8e5d116b9e59be7934febb12c41cce2038491ec8fdb743aeacaaf36d6e7597e4", size = 5259816, upload-time = "2025-08-22T10:34:37.482Z" }, - { url = "https://files.pythonhosted.org/packages/fa/fd/b266cfaab81d93a539040be699b5854dd24c84e523a1711ee5f615aa7000/lxml-6.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c238f0d0d40fdcb695c439fe5787fa69d40f45789326b3bb6ef0d61c4b588d6e", size = 5276162, upload-time = "2025-08-22T10:34:39.507Z" }, - { url = "https://files.pythonhosted.org/packages/25/6c/6f9610fbf1de002048e80585ea4719591921a0316a8565968737d9f125ca/lxml-6.0.1-cp314-cp314-win32.whl", hash = "sha256:537b6cf1c5ab88cfd159195d412edb3e434fee880f206cbe68dff9c40e17a68a", size = 3669595, upload-time = "2025-08-22T10:34:41.783Z" }, - { url = "https://files.pythonhosted.org/packages/72/a5/506775e3988677db24dc75a7b03e04038e0b3d114ccd4bccea4ce0116c15/lxml-6.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:911d0a2bb3ef3df55b3d97ab325a9ca7e438d5112c102b8495321105d25a441b", size = 4079818, upload-time = "2025-08-22T10:34:44.04Z" }, - { url = "https://files.pythonhosted.org/packages/0a/44/9613f300201b8700215856e5edd056d4e58dd23368699196b58877d4408b/lxml-6.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:2834377b0145a471a654d699bdb3a2155312de492142ef5a1d426af2c60a0a31", size = 3753901, upload-time = "2025-08-22T10:34:45.799Z" }, +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/d4/9326838b59dc36dfae42eec9656b97520f9997eee1de47b8316aaeed169c/lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", size = 8570663, upload-time = "2026-04-18T04:27:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a4/053745ce1f8303ccbb788b86c0db3a91b973675cefc42566a188637b7c40/lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93", size = 4624024, upload-time = "2026-04-18T04:27:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/90/97/a517944b20f8fd0932ad2109482bee4e29fe721416387a363306667941f6/lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", size = 4930895, upload-time = "2026-04-18T04:32:56.29Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/e08a970727d556caa040a44773c7b7e3ad0f0d73dedc863543e9a8b931f2/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a", size = 5093820, upload-time = "2026-04-18T04:32:58.94Z" }, + { url = "https://files.pythonhosted.org/packages/88/ee/2a5c2aa2c32016a226ca25d3e1056a8102ea6e1fe308bf50213586635400/lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105", size = 5005790, upload-time = "2026-04-18T04:33:01.272Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/a0db9be8f38ad6043ab9429487c128dd1d30f07956ef43040402f8da49e8/lxml-6.1.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485", size = 5630827, upload-time = "2026-04-18T04:33:04.036Z" }, + { url = "https://files.pythonhosted.org/packages/31/ba/3c13d3fc24b7cacf675f808a3a1baabf43a30d0cd24c98f94548e9aa58eb/lxml-6.1.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814", size = 5240445, upload-time = "2026-04-18T04:33:06.87Z" }, + { url = "https://files.pythonhosted.org/packages/55/ba/eeef4ccba09b2212fe239f46c1692a98db1878e0872ae320756488878a94/lxml-6.1.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32", size = 5350121, upload-time = "2026-04-18T04:33:09.365Z" }, + { url = "https://files.pythonhosted.org/packages/7e/01/1da87c7b587c38d0cbe77a01aae3b9c1c49ed47d76918ef3db8fc151b1ca/lxml-6.1.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad", size = 4694949, upload-time = "2026-04-18T04:33:11.628Z" }, + { url = "https://files.pythonhosted.org/packages/a1/88/7db0fe66d5aaf128443ee1623dec3db1576f3e4c17751ec0ef5866468590/lxml-6.1.0-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54", size = 5243901, upload-time = "2026-04-18T04:33:13.95Z" }, + { url = "https://files.pythonhosted.org/packages/00/a8/1346726af7d1f6fca1f11223ba34001462b0a3660416986d37641708d57c/lxml-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d", size = 5048054, upload-time = "2026-04-18T04:33:16.965Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b7/85057012f035d1a0c87e02f8c723ca3c3e6e0728bcf4cb62080b21b1c1e3/lxml-6.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69", size = 4777324, upload-time = "2026-04-18T04:33:19.832Z" }, + { url = "https://files.pythonhosted.org/packages/75/6c/ad2f94a91073ef570f33718040e8e160d5fb93331cf1ab3ca1323f939e2d/lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d", size = 5645702, upload-time = "2026-04-18T04:33:22.436Z" }, + { url = "https://files.pythonhosted.org/packages/3b/89/0bb6c0bd549c19004c60eea9dc554dd78fd647b72314ef25d460e0d208c6/lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5", size = 5232901, upload-time = "2026-04-18T04:33:26.21Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d9/d609a11fb567da9399f525193e2b49847b5a409cdebe737f06a8b7126bdc/lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d", size = 5261333, upload-time = "2026-04-18T04:33:28.984Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3a/ac3f99ec8ac93089e7dd556f279e0d14c24de0a74a507e143a2e4b496e7c/lxml-6.1.0-cp312-cp312-win32.whl", hash = "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f", size = 3596289, upload-time = "2026-04-18T04:27:42.819Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a7/0a915557538593cb1bbeedcd40e13c7a261822c26fecbbdb71dad0c2f540/lxml-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366", size = 3997059, upload-time = "2026-04-18T04:27:46.764Z" }, + { url = "https://files.pythonhosted.org/packages/92/96/a5dc078cf0126fbfbc35611d77ecd5da80054b5893e28fb213a5613b9e1d/lxml-6.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819", size = 3659552, upload-time = "2026-04-18T04:27:51.133Z" }, + { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, + { url = "https://files.pythonhosted.org/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" }, + { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, + { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, + { url = "https://files.pythonhosted.org/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" }, + { url = "https://files.pythonhosted.org/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578, upload-time = "2026-04-18T04:33:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, + { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" }, + { url = "https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" }, + { url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" }, + { url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522, upload-time = "2026-04-18T04:34:14.89Z" }, + { url = "https://files.pythonhosted.org/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310, upload-time = "2026-04-18T04:34:17.652Z" }, + { url = "https://files.pythonhosted.org/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799, upload-time = "2026-04-18T04:34:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693, upload-time = "2026-04-18T04:34:23.541Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708, upload-time = "2026-04-18T04:34:26.001Z" }, + { url = "https://files.pythonhosted.org/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737, upload-time = "2026-04-18T04:34:28.32Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817, upload-time = "2026-04-18T04:34:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" }, + { url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" }, + { url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" }, + { url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" }, + { url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" }, + { url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" }, + { url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" }, + { url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" }, + { url = "https://files.pythonhosted.org/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008, upload-time = "2026-04-18T04:34:51.503Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451, upload-time = "2026-04-18T04:34:54.263Z" }, + { url = "https://files.pythonhosted.org/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135, upload-time = "2026-04-18T04:34:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126, upload-time = "2026-04-18T04:34:59.704Z" }, + { url = "https://files.pythonhosted.org/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579, upload-time = "2026-04-18T04:35:02.658Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206, upload-time = "2026-04-18T04:35:05.175Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906, upload-time = "2026-04-18T04:35:08.098Z" }, + { url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" }, + { url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" }, + { url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" }, ] [[package]] @@ -491,134 +533,157 @@ wheels = [ [[package]] name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] [[package]] name = "more-itertools" -version = "10.8.0" +version = "11.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, + { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, ] [[package]] name = "numpy" -version = "2.3.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" }, - { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" }, - { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" }, - { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" }, - { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" }, - { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" }, - { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" }, - { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" }, - { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" }, - { url = "https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf", size = 20949588, upload-time = "2025-09-09T15:56:59.087Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7", size = 14177802, upload-time = "2025-09-09T15:57:01.73Z" }, - { url = "https://files.pythonhosted.org/packages/35/c7/477a83887f9de61f1203bad89cf208b7c19cc9fef0cebef65d5a1a0619f2/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6", size = 5106537, upload-time = "2025-09-09T15:57:03.765Z" }, - { url = "https://files.pythonhosted.org/packages/52/47/93b953bd5866a6f6986344d045a207d3f1cfbad99db29f534ea9cee5108c/numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7", size = 6640743, upload-time = "2025-09-09T15:57:07.921Z" }, - { url = "https://files.pythonhosted.org/packages/23/83/377f84aaeb800b64c0ef4de58b08769e782edcefa4fea712910b6f0afd3c/numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c", size = 14278881, upload-time = "2025-09-09T15:57:11.349Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93", size = 16636301, upload-time = "2025-09-09T15:57:14.245Z" }, - { url = "https://files.pythonhosted.org/packages/a2/59/1287924242eb4fa3f9b3a2c30400f2e17eb2707020d1c5e3086fe7330717/numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae", size = 16053645, upload-time = "2025-09-09T15:57:16.534Z" }, - { url = "https://files.pythonhosted.org/packages/e6/93/b3d47ed882027c35e94ac2320c37e452a549f582a5e801f2d34b56973c97/numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86", size = 18578179, upload-time = "2025-09-09T15:57:18.883Z" }, - { url = "https://files.pythonhosted.org/packages/20/d9/487a2bccbf7cc9d4bfc5f0f197761a5ef27ba870f1e3bbb9afc4bbe3fcc2/numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8", size = 6312250, upload-time = "2025-09-09T15:57:21.296Z" }, - { url = "https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf", size = 12783269, upload-time = "2025-09-09T15:57:23.034Z" }, - { url = "https://files.pythonhosted.org/packages/fa/75/67b8ca554bbeaaeb3fac2e8bce46967a5a06544c9108ec0cf5cece559b6c/numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5", size = 10195314, upload-time = "2025-09-09T15:57:25.045Z" }, - { url = "https://files.pythonhosted.org/packages/11/d0/0d1ddec56b162042ddfafeeb293bac672de9b0cfd688383590090963720a/numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc", size = 21048025, upload-time = "2025-09-09T15:57:27.257Z" }, - { url = "https://files.pythonhosted.org/packages/36/9e/1996ca6b6d00415b6acbdd3c42f7f03ea256e2c3f158f80bd7436a8a19f3/numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc", size = 14301053, upload-time = "2025-09-09T15:57:30.077Z" }, - { url = "https://files.pythonhosted.org/packages/05/24/43da09aa764c68694b76e84b3d3f0c44cb7c18cdc1ba80e48b0ac1d2cd39/numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b", size = 5229444, upload-time = "2025-09-09T15:57:32.733Z" }, - { url = "https://files.pythonhosted.org/packages/bc/14/50ffb0f22f7218ef8af28dd089f79f68289a7a05a208db9a2c5dcbe123c1/numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19", size = 6738039, upload-time = "2025-09-09T15:57:34.328Z" }, - { url = "https://files.pythonhosted.org/packages/55/52/af46ac0795e09657d45a7f4db961917314377edecf66db0e39fa7ab5c3d3/numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30", size = 14352314, upload-time = "2025-09-09T15:57:36.255Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b1/dc226b4c90eb9f07a3fff95c2f0db3268e2e54e5cce97c4ac91518aee71b/numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e", size = 16701722, upload-time = "2025-09-09T15:57:38.622Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9d/9d8d358f2eb5eced14dba99f110d83b5cd9a4460895230f3b396ad19a323/numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3", size = 16132755, upload-time = "2025-09-09T15:57:41.16Z" }, - { url = "https://files.pythonhosted.org/packages/b6/27/b3922660c45513f9377b3fb42240bec63f203c71416093476ec9aa0719dc/numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea", size = 18651560, upload-time = "2025-09-09T15:57:43.459Z" }, - { url = "https://files.pythonhosted.org/packages/5b/8e/3ab61a730bdbbc201bb245a71102aa609f0008b9ed15255500a99cd7f780/numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd", size = 6442776, upload-time = "2025-09-09T15:57:45.793Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3a/e22b766b11f6030dc2decdeff5c2fb1610768055603f9f3be88b6d192fb2/numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d", size = 12927281, upload-time = "2025-09-09T15:57:47.492Z" }, - { url = "https://files.pythonhosted.org/packages/7b/42/c2e2bc48c5e9b2a83423f99733950fbefd86f165b468a3d85d52b30bf782/numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1", size = 10265275, upload-time = "2025-09-09T15:57:49.647Z" }, - { url = "https://files.pythonhosted.org/packages/6b/01/342ad585ad82419b99bcf7cebe99e61da6bedb89e213c5fd71acc467faee/numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593", size = 20951527, upload-time = "2025-09-09T15:57:52.006Z" }, - { url = "https://files.pythonhosted.org/packages/ef/d8/204e0d73fc1b7a9ee80ab1fe1983dd33a4d64a4e30a05364b0208e9a241a/numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652", size = 14186159, upload-time = "2025-09-09T15:57:54.407Z" }, - { url = "https://files.pythonhosted.org/packages/22/af/f11c916d08f3a18fb8ba81ab72b5b74a6e42ead4c2846d270eb19845bf74/numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7", size = 5114624, upload-time = "2025-09-09T15:57:56.5Z" }, - { url = "https://files.pythonhosted.org/packages/fb/11/0ed919c8381ac9d2ffacd63fd1f0c34d27e99cab650f0eb6f110e6ae4858/numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a", size = 6642627, upload-time = "2025-09-09T15:57:58.206Z" }, - { url = "https://files.pythonhosted.org/packages/ee/83/deb5f77cb0f7ba6cb52b91ed388b47f8f3c2e9930d4665c600408d9b90b9/numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe", size = 14296926, upload-time = "2025-09-09T15:58:00.035Z" }, - { url = "https://files.pythonhosted.org/packages/77/cc/70e59dcb84f2b005d4f306310ff0a892518cc0c8000a33d0e6faf7ca8d80/numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421", size = 16638958, upload-time = "2025-09-09T15:58:02.738Z" }, - { url = "https://files.pythonhosted.org/packages/b6/5a/b2ab6c18b4257e099587d5b7f903317bd7115333ad8d4ec4874278eafa61/numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021", size = 16071920, upload-time = "2025-09-09T15:58:05.029Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f1/8b3fdc44324a259298520dd82147ff648979bed085feeacc1250ef1656c0/numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf", size = 18577076, upload-time = "2025-09-09T15:58:07.745Z" }, - { url = "https://files.pythonhosted.org/packages/f0/a1/b87a284fb15a42e9274e7fcea0dad259d12ddbf07c1595b26883151ca3b4/numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0", size = 6366952, upload-time = "2025-09-09T15:58:10.096Z" }, - { url = "https://files.pythonhosted.org/packages/70/5f/1816f4d08f3b8f66576d8433a66f8fa35a5acfb3bbd0bf6c31183b003f3d/numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8", size = 12919322, upload-time = "2025-09-09T15:58:12.138Z" }, - { url = "https://files.pythonhosted.org/packages/8c/de/072420342e46a8ea41c324a555fa90fcc11637583fb8df722936aed1736d/numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe", size = 10478630, upload-time = "2025-09-09T15:58:14.64Z" }, - { url = "https://files.pythonhosted.org/packages/d5/df/ee2f1c0a9de7347f14da5dd3cd3c3b034d1b8607ccb6883d7dd5c035d631/numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00", size = 21047987, upload-time = "2025-09-09T15:58:16.889Z" }, - { url = "https://files.pythonhosted.org/packages/d6/92/9453bdc5a4e9e69cf4358463f25e8260e2ffc126d52e10038b9077815989/numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a", size = 14301076, upload-time = "2025-09-09T15:58:20.343Z" }, - { url = "https://files.pythonhosted.org/packages/13/77/1447b9eb500f028bb44253105bd67534af60499588a5149a94f18f2ca917/numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d", size = 5229491, upload-time = "2025-09-09T15:58:22.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/f9/d72221b6ca205f9736cb4b2ce3b002f6e45cd67cd6a6d1c8af11a2f0b649/numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a", size = 6737913, upload-time = "2025-09-09T15:58:24.569Z" }, - { url = "https://files.pythonhosted.org/packages/3c/5f/d12834711962ad9c46af72f79bb31e73e416ee49d17f4c797f72c96b6ca5/numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54", size = 14352811, upload-time = "2025-09-09T15:58:26.416Z" }, - { url = "https://files.pythonhosted.org/packages/a1/0d/fdbec6629d97fd1bebed56cd742884e4eead593611bbe1abc3eb40d304b2/numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e", size = 16702689, upload-time = "2025-09-09T15:58:28.831Z" }, - { url = "https://files.pythonhosted.org/packages/9b/09/0a35196dc5575adde1eb97ddfbc3e1687a814f905377621d18ca9bc2b7dd/numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097", size = 16133855, upload-time = "2025-09-09T15:58:31.349Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ca/c9de3ea397d576f1b6753eaa906d4cdef1bf97589a6d9825a349b4729cc2/numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970", size = 18652520, upload-time = "2025-09-09T15:58:33.762Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c2/e5ed830e08cd0196351db55db82f65bc0ab05da6ef2b72a836dcf1936d2f/numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5", size = 6515371, upload-time = "2025-09-09T15:58:36.04Z" }, - { url = "https://files.pythonhosted.org/packages/47/c7/b0f6b5b67f6788a0725f744496badbb604d226bf233ba716683ebb47b570/numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f", size = 13112576, upload-time = "2025-09-09T15:58:37.927Z" }, - { url = "https://files.pythonhosted.org/packages/06/b9/33bba5ff6fb679aa0b1f8a07e853f002a6b04b9394db3069a1270a7784ca/numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b", size = 10545953, upload-time = "2025-09-09T15:58:40.576Z" }, +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, ] [[package]] name = "p2api" -version = "1.0.10" +version = "1.0.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "keyring" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/94/96ece6cf22552578a16f82ccedabc1d067f65ba871cfec60324a2eb51d12/p2api-1.0.10.tar.gz", hash = "sha256:9d9d41d978fb5143c9eb1e7119bd8bd7ff1c0f74b267d2fef425e59c717b35ef", size = 25017, upload-time = "2024-09-12T12:56:51.453Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/54/2181f0b2c85c3515d01b8a99fe70bd8e1dc0c673a4f6f1dbd512f26ce441/p2api-1.0.11.tar.gz", hash = "sha256:779fcb6f5502a210eba39e64026348dcbcfe256208afde5d199645d9d2b943d6", size = 24994, upload-time = "2026-02-19T09:41:41.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/a3/07ff4a0be52675076fea31558ed4c4e880887df606bcdd395ac8b2802298/p2api-1.0.10-py2.py3-none-any.whl", hash = "sha256:0d1d1edfbbc9202174147fa34117522db6c27af4b4d3e86adad7458f2df6965b", size = 26400, upload-time = "2024-09-12T12:56:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ce/7158780102f6878279ee358d36f8ad13e45061a0a8c3bf552d37cfe61c22/p2api-1.0.11-py2.py3-none-any.whl", hash = "sha256:8eb1b51c0cee59b3292434dffc36c4b0b34c787b1728aeba73c036d017d8901c", size = 26415, upload-time = "2026-02-19T09:41:40.154Z" }, ] [[package]] name = "packaging" -version = "25.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] @@ -632,30 +697,30 @@ wheels = [ [[package]] name = "pyastrosalt" -version = "0.2.0" +version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "defusedxml" }, { name = "requests" }, { name = "types-defusedxml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/08/2a45dac29e5e7d2084425209ebf55c6cc2ef75d54c1b3dd65cb54bb9d09f/pyastrosalt-0.2.0.tar.gz", hash = "sha256:4e6d14eefaa34520fca1ac6f53675053c42deb5526d4d8605a940ea1dca316e4", size = 16204, upload-time = "2026-04-24T17:05:04.279Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/79/fee35e0b4ae20729caf841605973a0bbc82703ec7494aa3a306729e5844e/pyastrosalt-0.2.1.tar.gz", hash = "sha256:fca18a4917432b76027556e9796e51144a11eaeca2d1a974e0625e84ed8d3699", size = 16185, upload-time = "2026-04-28T15:24:27.231Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/74/0ba659828893ce394ba999f64dabbd0f051039d7ab474363394d54c97b45/pyastrosalt-0.2.0-py3-none-any.whl", hash = "sha256:fe94f6290c7e2d007256b7e7b615ad7b6c041f347dcf5df8385e572e7c10dd79", size = 11986, upload-time = "2026-04-24T17:05:03.106Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/4c17fa38c7dd8c229915551b25857b790b5067207a7e5588f28a8a8e0759/pyastrosalt-0.2.1-py3-none-any.whl", hash = "sha256:372c930e02f430b01afcae6e3044a2b4142e28257a57c1e8a3639bdfcfcd3cce", size = 12156, upload-time = "2026-04-28T15:24:26.16Z" }, ] [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] name = "pydantic" -version = "2.11.9" +version = "2.13.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -663,65 +728,98 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" }, + { url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981, upload-time = "2026-04-20T14:46:41.402Z" }, ] [[package]] name = "pydantic-core" -version = "2.33.2" +version = "2.46.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, - { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, - { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, - { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, - { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, - { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, - { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, - { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, - { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/cb/5b47425556ecc1f3fe18ed2a0083188aa46e1dd812b06e406475b3a5d536/pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67", size = 2101946, upload-time = "2026-04-20T14:40:52.581Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089", size = 1951612, upload-time = "2026-04-20T14:42:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/50/6e/b7348fd30d6556d132cddd5bd79f37f96f2601fe0608afac4f5fb01ec0b3/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0", size = 1977027, upload-time = "2026-04-20T14:42:02.001Z" }, + { url = "https://files.pythonhosted.org/packages/82/11/31d60ee2b45540d3fb0b29302a393dbc01cd771c473f5b5147bcd353e593/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789", size = 2063008, upload-time = "2026-04-20T14:44:17.952Z" }, + { url = "https://files.pythonhosted.org/packages/8a/db/3a9d1957181b59258f44a2300ab0f0be9d1e12d662a4f57bb31250455c52/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d", size = 2233082, upload-time = "2026-04-20T14:40:57.934Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e1/3277c38792aeb5cfb18c2f0c5785a221d9ff4e149abbe1184d53d5f72273/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c", size = 2304615, upload-time = "2026-04-20T14:42:12.584Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d5/e3d9717c9eba10855325650afd2a9cba8e607321697f18953af9d562da2f/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395", size = 2094380, upload-time = "2026-04-20T14:43:05.522Z" }, + { url = "https://files.pythonhosted.org/packages/a1/20/abac35dedcbfd66c6f0b03e4e3564511771d6c9b7ede10a362d03e110d9b/pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396", size = 2135429, upload-time = "2026-04-20T14:41:55.549Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a5/41bfd1df69afad71b5cf0535055bccc73022715ad362edbc124bc1e021d7/pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d", size = 2174582, upload-time = "2026-04-20T14:41:45.96Z" }, + { url = "https://files.pythonhosted.org/packages/79/65/38d86ea056b29b2b10734eb23329b7a7672ca604df4f2b6e9c02d4ee22fe/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca", size = 2187533, upload-time = "2026-04-20T14:40:55.367Z" }, + { url = "https://files.pythonhosted.org/packages/b6/55/a1129141678a2026badc539ad1dee0a71d06f54c2f06a4bd68c030ac781b/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976", size = 2332985, upload-time = "2026-04-20T14:44:13.05Z" }, + { url = "https://files.pythonhosted.org/packages/d7/60/cb26f4077719f709e54819f4e8e1d43f4091f94e285eb6bd21e1190a7b7c/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b", size = 2373670, upload-time = "2026-04-20T14:41:53.421Z" }, + { url = "https://files.pythonhosted.org/packages/6b/7e/c3f21882bdf1d8d086876f81b5e296206c69c6082551d776895de7801fa0/pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4", size = 1966722, upload-time = "2026-04-20T14:44:30.588Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1", size = 2072970, upload-time = "2026-04-20T14:42:54.248Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f8/a989b21cc75e9a32d24192ef700eea606521221a89faa40c919ce884f2b1/pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72", size = 2035963, upload-time = "2026-04-20T14:44:20.4Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109, upload-time = "2026-04-20T14:41:24.219Z" }, + { url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820, upload-time = "2026-04-20T14:42:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785, upload-time = "2026-04-20T14:42:31.648Z" }, + { url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761, upload-time = "2026-04-20T14:40:37.093Z" }, + { url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989, upload-time = "2026-04-20T14:42:59.254Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975, upload-time = "2026-04-20T14:41:32.287Z" }, + { url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325, upload-time = "2026-04-20T14:42:47.685Z" }, + { url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368, upload-time = "2026-04-20T14:41:22.766Z" }, + { url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908, upload-time = "2026-04-20T14:42:52.044Z" }, + { url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422, upload-time = "2026-04-20T14:43:29.55Z" }, + { url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709, upload-time = "2026-04-20T14:42:16.134Z" }, + { url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428, upload-time = "2026-04-20T14:41:10.974Z" }, + { url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601, upload-time = "2026-04-20T14:41:42.128Z" }, + { url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517, upload-time = "2026-04-20T14:43:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802, upload-time = "2026-04-20T14:43:38.507Z" }, + { url = "https://files.pythonhosted.org/packages/7f/db/a7bcb4940183fda36022cd18ba8dd12f2dff40740ec7b58ce7457befa416/pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b", size = 2097614, upload-time = "2026-04-20T14:44:38.374Z" }, + { url = "https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018", size = 1951896, upload-time = "2026-04-20T14:40:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/87/92/37cf4049d1636996e4b888c05a501f40a43ff218983a551d57f9d5e14f0d/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34", size = 1979314, upload-time = "2026-04-20T14:41:49.446Z" }, + { url = "https://files.pythonhosted.org/packages/d8/36/9ff4d676dfbdfb2d591cf43f3d90ded01e15b1404fd101180ed2d62a2fd3/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7", size = 2056133, upload-time = "2026-04-20T14:42:23.574Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f0/405b442a4d7ba855b06eec8b2bf9c617d43b8432d099dfdc7bf999293495/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2", size = 2228726, upload-time = "2026-04-20T14:44:22.816Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f8/65cd92dd5a0bd89ba277a98ecbfaf6fc36bbd3300973c7a4b826d6ab1391/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0087084960f209a9a4af50ecd1fb063d9ad3658c07bb81a7a53f452dacbfb2ba", size = 2301214, upload-time = "2026-04-20T14:44:48.792Z" }, + { url = "https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f", size = 2099927, upload-time = "2026-04-20T14:41:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/6d/53/269caf30e0096e0a8a8f929d1982a27b3879872cca2d917d17c2f9fdf4fe/pydantic_core-2.46.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f1771ce258afb3e4201e67d154edbbae712a76a6081079fe247c2f53c6322c22", size = 2128789, upload-time = "2026-04-20T14:41:15.868Z" }, + { url = "https://files.pythonhosted.org/packages/00/b0/1a6d9b6a587e118482910c244a1c5acf4d192604174132efd12bf0ac486f/pydantic_core-2.46.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7610b6a5242a6c736d8ad47fd5fff87fcfe8f833b281b1c409c3d6835d9227f", size = 2173815, upload-time = "2026-04-20T14:44:25.152Z" }, + { url = "https://files.pythonhosted.org/packages/87/56/e7e00d4041a7e62b5a40815590114db3b535bf3ca0bf4dca9f16cef25246/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127", size = 2181608, upload-time = "2026-04-20T14:41:28.933Z" }, + { url = "https://files.pythonhosted.org/packages/e8/22/4bd23c3d41f7c185d60808a1de83c76cf5aeabf792f6c636a55c3b1ec7f9/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c", size = 2326968, upload-time = "2026-04-20T14:42:03.962Z" }, + { url = "https://files.pythonhosted.org/packages/24/ac/66cd45129e3915e5ade3b292cb3bc7fd537f58f8f8dbdaba6170f7cabb74/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1", size = 2369842, upload-time = "2026-04-20T14:41:35.52Z" }, + { url = "https://files.pythonhosted.org/packages/a2/51/dd4248abb84113615473aa20d5545b7c4cd73c8644003b5259686f93996c/pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505", size = 1959661, upload-time = "2026-04-20T14:41:00.042Z" }, + { url = "https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e", size = 2071686, upload-time = "2026-04-20T14:43:16.471Z" }, + { url = "https://files.pythonhosted.org/packages/8c/db/1cf77e5247047dfee34bc01fa9bca134854f528c8eb053e144298893d370/pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df", size = 2026907, upload-time = "2026-04-20T14:43:31.732Z" }, + { url = "https://files.pythonhosted.org/packages/57/c0/b3df9f6a543276eadba0a48487b082ca1f201745329d97dbfa287034a230/pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf", size = 2095047, upload-time = "2026-04-20T14:42:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/57/886a938073b97556c168fd99e1a7305bb363cd30a6d2c76086bf0587b32a/pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee", size = 1934329, upload-time = "2026-04-20T14:43:49.655Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7c/b42eaa5c34b13b07ecb51da21761297a9b8eb43044c864a035999998f328/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a", size = 1974847, upload-time = "2026-04-20T14:42:10.737Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9b/92b42db6543e7de4f99ae977101a2967b63122d4b6cf7773812da2d7d5b5/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c", size = 2041742, upload-time = "2026-04-20T14:40:44.262Z" }, + { url = "https://files.pythonhosted.org/packages/0f/19/46fbe1efabb5aa2834b43b9454e70f9a83ad9c338c1291e48bdc4fecf167/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1", size = 2236235, upload-time = "2026-04-20T14:41:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/77/da/b3f95bc009ad60ec53120f5d16c6faa8cabdbe8a20d83849a1f2b8728148/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13936129ce841f2a5ddf6f126fea3c43cd128807b5a59588c37cf10178c2e64", size = 2282633, upload-time = "2026-04-20T14:44:33.271Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6e/401336117722e28f32fb8220df676769d28ebdf08f2f4469646d404c43a3/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28b5f2ef03416facccb1c6ef744c69793175fd27e44ef15669201601cf423acb", size = 2109679, upload-time = "2026-04-20T14:44:41.065Z" }, + { url = "https://files.pythonhosted.org/packages/fc/53/b289f9bc8756a32fe718c46f55afaeaf8d489ee18d1a1e7be1db73f42cc4/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:830d1247d77ad23852314f069e9d7ddafeec5f684baf9d7e7065ed46a049c4e6", size = 2108342, upload-time = "2026-04-20T14:42:50.144Z" }, + { url = "https://files.pythonhosted.org/packages/10/5b/8292fc7c1f9111f1b2b7c1b0dcf1179edcd014fc3ea4517499f50b829d71/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0793c90c1a3c74966e7975eaef3ed30ebdff3260a0f815a62a22adc17e4c01c", size = 2157208, upload-time = "2026-04-20T14:42:08.133Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9e/f80044e9ec07580f057a89fc131f78dda7a58751ddf52bbe05eaf31db50f/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47", size = 2167237, upload-time = "2026-04-20T14:42:25.412Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/6781a1b037f3b96be9227edbd1101f6d3946746056231bf4ac48cdff1a8d/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab", size = 2312540, upload-time = "2026-04-20T14:40:40.313Z" }, + { url = "https://files.pythonhosted.org/packages/3e/db/19c0839feeb728e7df03255581f198dfdf1c2aeb1e174a8420b63c5252e5/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba", size = 2369556, upload-time = "2026-04-20T14:41:09.427Z" }, + { url = "https://files.pythonhosted.org/packages/e0/15/3228774cb7cd45f5f721ddf1b2242747f4eb834d0c491f0c02d606f09fed/pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56", size = 1949756, upload-time = "2026-04-20T14:41:25.717Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2a/c79cf53fd91e5a87e30d481809f52f9a60dd221e39de66455cf04deaad37/pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8", size = 2051305, upload-time = "2026-04-20T14:43:18.627Z" }, + { url = "https://files.pythonhosted.org/packages/0b/db/d8182a7f1d9343a032265aae186eb063fe26ca4c40f256b21e8da4498e89/pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374", size = 2026310, upload-time = "2026-04-20T14:41:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/34/42/f426db557e8ab2791bc7562052299944a118655496fbff99914e564c0a94/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b12dd51f1187c2eb489af8e20f880362db98e954b54ab792fa5d92e8bcc6b803", size = 2091877, upload-time = "2026-04-20T14:43:27.091Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4f/86a832a9d14df58e663bfdf4627dc00d3317c2bd583c4fb23390b0f04b8e/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f00a0961b125f1a47af7bcc17f00782e12f4cd056f83416006b30111d941dfa3", size = 1932428, upload-time = "2026-04-20T14:40:45.781Z" }, + { url = "https://files.pythonhosted.org/packages/11/1a/fe857968954d93fb78e0d4b6df5c988c74c4aaa67181c60be7cfe327c0ca/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57697d7c056aca4bbb680200f96563e841a6386ac1129370a0102592f4dddff5", size = 1997550, upload-time = "2026-04-20T14:44:02.425Z" }, + { url = "https://files.pythonhosted.org/packages/17/eb/9d89ad2d9b0ba8cd65393d434471621b98912abb10fbe1df08e480ba57b5/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd35aa21299def8db7ef4fe5c4ff862941a9a158ca7b63d61e66fe67d30416b4", size = 2137657, upload-time = "2026-04-20T14:42:45.149Z" }, ] [[package]] name = "pydantic-settings" -version = "2.10.1" +version = "2.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/98/c8345dccdc31de4228c039a98f6467a941e39558da41c1744fbe29fa5666/pydantic_settings-2.14.0.tar.gz", hash = "sha256:24285fd4b0e0c06507dd9fdfd331ee23794305352aaec8fc4eb92d4047aeb67d", size = 235709, upload-time = "2026-04-20T13:37:40.293Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, + { url = "https://files.pythonhosted.org/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" }, ] [[package]] @@ -744,16 +842,16 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -762,18 +860,18 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] name = "python-dotenv" -version = "1.1.1" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] [[package]] @@ -787,33 +885,53 @@ wheels = [ [[package]] name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] [[package]] name = "requests" -version = "2.32.5" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -821,31 +939,22 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] name = "secretstorage" -version = "3.4.0" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "jeepney" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/9f/11ef35cf1027c1339552ea7bfe6aaa74a8516d8b5caf6e7d338daf54fd80/secretstorage-3.4.0.tar.gz", hash = "sha256:c46e216d6815aff8a8a18706a2fbfd8d53fcbb0dce99301881687a1b0289ef7c", size = 19748, upload-time = "2025-09-09T16:42:13.859Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/ff/2e2eed29e02c14a5cb6c57f09b2d5b40e65d6cc71f45b52e0be295ccbc2f/secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e", size = 15272, upload-time = "2025-09-09T16:42:12.744Z" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, ] [[package]] @@ -868,11 +977,11 @@ wheels = [ [[package]] name = "textcase" -version = "0.4.3" +version = "0.4.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/e6/5499333512a7008cfe795c4e2db404c34c61da3446727b31bfd2fcf79783/textcase-0.4.3.tar.gz", hash = "sha256:1900dda7d5fc8cf44c414677b9be8b66b2354d9ff4107934ed577f11c1d1210a", size = 6946, upload-time = "2025-04-18T16:13:21.712Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/08/991940591c7a8de25505a4e43642d1c2b54f43c3ed76f8c90f1df6ee9525/textcase-0.4.5.tar.gz", hash = "sha256:97fd08754b7dba9bfa5daf4ace474645f6b66409fb00f483c5de729e730f8e52", size = 6988, upload-time = "2025-10-10T15:11:25.476Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/a3/7e8d624362ca16a51df435d14c08e07adf266c27d16fad422f954a3abbbd/textcase-0.4.3-py3-none-any.whl", hash = "sha256:2f1a1c069ba60e224455ae6d45ca35344ad6a2ffffd05d6bd8161c0c34515f30", size = 6474, upload-time = "2025-04-18T16:13:20.621Z" }, + { url = "https://files.pythonhosted.org/packages/5b/48/6ea42c749608cfca883d5d8fae03edbcde636090d2bc751fd5da9157b451/textcase-0.4.5-py3-none-any.whl", hash = "sha256:bd5d6aaf653b339e3ac60ad96cfc960a94219a97da464eddeed7084f41774937", size = 6503, upload-time = "2025-10-10T15:11:24.294Z" }, ] [[package]] @@ -958,21 +1067,21 @@ wheels = [ [[package]] name = "typing-inspection" -version = "0.4.1" +version = "0.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] From 0d51eaf325f17de6f48e89c1c54c882437a37eae Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Tue, 28 Apr 2026 20:18:01 +0200 Subject: [PATCH 165/171] Revert dependency issues because of Pydantic issue It seems that pydantic_settings 2.14 did not pick up environment variable values. --- uv.lock | 855 ++++++++++++++++++++++++-------------------------------- 1 file changed, 373 insertions(+), 482 deletions(-) diff --git a/uv.lock b/uv.lock index d53fc25..11e09b2 100644 --- a/uv.lock +++ b/uv.lock @@ -79,20 +79,21 @@ wheels = [ [[package]] name = "anyio" -version = "4.13.0" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, + { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, ] [[package]] name = "astropy" -version = "7.2.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astropy-iers-data" }, @@ -101,25 +102,31 @@ dependencies = [ { name = "pyerfa" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/92/2dce2d48347efc3346d08ca7995b152d242ebd170c571f7c9346468d8427/astropy-7.2.0.tar.gz", hash = "sha256:ae48bc26b1feaeb603cd94bd1fa1aa39137a115fe931b7f13787ab420e8c3070", size = 7057774, upload-time = "2025-11-25T22:36:41.916Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/91/124d020cea78e4e4b6db7ff726c2c2e4a5865293d0a4355d13b0312d99f1/astropy-7.1.0.tar.gz", hash = "sha256:c8f254322295b1b8cf24303d6f155bf7efdb6c1282882b966ce3040eff8c53c5", size = 6976116, upload-time = "2025-05-20T13:40:10.557Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/6d/6330a844bad8dfc4875e0f2fa1db1fee87837ba9805aa8a8d048c071363a/astropy-7.2.0-cp311-abi3-macosx_10_9_x86_64.whl", hash = "sha256:efac04df4cc488efe630c2fff1992d6516dfb16a06e197fb68bc9e8e3b85def1", size = 6442332, upload-time = "2025-11-25T22:36:23.6Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ba/3418133ba144dfcd1530bca5a6b695f4cdd21a8abaaa2ac4e5450d11b028/astropy-7.2.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:52e9a7d9c86b21f1af911a2930cd0c4a275fb302d455c89e11eedaffef6f2ad0", size = 6413656, upload-time = "2025-11-25T22:36:26.548Z" }, - { url = "https://files.pythonhosted.org/packages/be/ba/05e43b5a7d738316a097fa78524d3eaaff5986294b4a052d4adb3c45e7c0/astropy-7.2.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97c370421b9bb13d4c762c7af06d172bad7c01bd5bcf88314f6913c3c235b770", size = 9758867, upload-time = "2025-11-25T22:36:28.661Z" }, - { url = "https://files.pythonhosted.org/packages/c3/1c/f06ad85180e7dd9855aa5ede901bfc2be858d7bee17d4e978a14c0ecec14/astropy-7.2.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f39ce2c80211fbceb005d377a5478cd0d66c42aa1498d252f2239fe5a025c24", size = 9789007, upload-time = "2025-11-25T22:36:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/f8/fb/e4d35194a5009d7a73333079481a4ef1380a255d67b9c1db578151a5fb50/astropy-7.2.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ad4d71db994d45f046a1a5449000cf0f88ab6367cb67658500654a0586d6ab19", size = 9748547, upload-time = "2025-11-25T22:36:33.154Z" }, - { url = "https://files.pythonhosted.org/packages/36/ea/f990730978ae0a7a34705f885d2f3806928c5f0bc22eefd6a1a23539cc32/astropy-7.2.0-cp311-abi3-win32.whl", hash = "sha256:95161f26602433176483e8bde8ab1a8ca09148f5b4bf5190569a26d381091598", size = 6237228, upload-time = "2025-11-25T22:36:35.236Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bc/f4378f586dd63902c37d16f68f35f7d555b3b32e08ac6b1d633eb0a48805/astropy-7.2.0-cp311-abi3-win_amd64.whl", hash = "sha256:dc7c340ba1713e55c93071b32033f3153470a0f663a4d539c03a7c9b44020790", size = 6362868, upload-time = "2025-11-25T22:36:37.784Z" }, - { url = "https://files.pythonhosted.org/packages/77/79/b6d4bf01913cfd4ce0cd4c1be5916beccdb92b2970bab8c827984231eae6/astropy-7.2.0-cp311-abi3-win_arm64.whl", hash = "sha256:0c428735a3f15b05c2095bc6ccb5f98a64bc99fb7015866af19ff8492420ddaf", size = 6221756, upload-time = "2025-11-25T22:36:39.852Z" }, + { url = "https://files.pythonhosted.org/packages/cf/9a/ed2b35b55e28a6317471b61456d2feda7798b2dd3601e17859620e8eae4c/astropy-7.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e0fec2f4b5265caab68020eaa320704e7ce9433ae8dbea75c300468fed695437", size = 6381273, upload-time = "2025-05-20T13:39:45.481Z" }, + { url = "https://files.pythonhosted.org/packages/5c/45/333bc1072f3b2ac31aec33063bb7122661405a97cb7fec702e95af707bd4/astropy-7.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4bb022f863cf13eefeb406692f58824c0d9bdb1aa36ae786e87c096d8ebdd07", size = 6301716, upload-time = "2025-05-20T13:39:47.339Z" }, + { url = "https://files.pythonhosted.org/packages/58/90/bfb7a1b5d9e3401967e351cf31add576cddf7466d2030cc6f4d1d841a18d/astropy-7.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811a4aedd8fbf6d7611d64d40af1f0c1c1e6621e5992e7e1a7b5fec47dc1fa1", size = 10096600, upload-time = "2025-05-20T13:39:49.169Z" }, + { url = "https://files.pythonhosted.org/packages/fb/69/a34f20db7146912f25e2487c5283f1ae2aed5d24f615fa976439ece35f7e/astropy-7.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:654261df547c150e5b6a022f3785f47a2e547e0cc1c06fbcf6293b5f4e85722a", size = 10160320, upload-time = "2025-05-20T13:39:51.079Z" }, + { url = "https://files.pythonhosted.org/packages/7a/be/0b874f551acbac27ff3d5d73bdf3c0860a8b78c6db574b19e6d0b7d363e4/astropy-7.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6bd25761ba1385bb99a189401fbc486e0884d97129e271b655b6efa956a12a77", size = 10137307, upload-time = "2025-05-20T13:39:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/ea/1c/132241ab2006a52b4809ee82a71fde793c0159916ae45dae374b0412b037/astropy-7.1.0-cp312-cp312-win32.whl", hash = "sha256:f637e39622b23750a12b19ab4642f2e3970f6cb84f2228587725f15bf1d80d03", size = 6152433, upload-time = "2025-05-20T13:39:54.933Z" }, + { url = "https://files.pythonhosted.org/packages/11/39/0a38241008905a9c1048f7b351310deee71cf42e0de1f929e84a10878864/astropy-7.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:06ba650db237557912fdd7bb8ffdd87838e23e58b0fa0001b4d43c2bb5a79412", size = 6279663, upload-time = "2025-05-20T13:39:56.274Z" }, + { url = "https://files.pythonhosted.org/packages/a3/d6/ec68703aff787ca1bb2ed92a109256a6e4d9975aeee0b4bd2b0ab84b2993/astropy-7.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:116149abdebafab193f7ca16427bd6bb4afc111677c78c32f48cffbc0ecb6a18", size = 6375607, upload-time = "2025-05-20T13:39:57.6Z" }, + { url = "https://files.pythonhosted.org/packages/2b/79/0f6eacbe575c3b677d70193c22b68c3a95bec68204ccf53068b657c7ed06/astropy-7.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:734d5e93e76d2e2175f991b17ca130463f2edb56c107fbc07c6532ca461a6ea7", size = 6296640, upload-time = "2025-05-20T13:39:58.973Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c1/06bd8bd17b2302c05d75079ca89d79825dede63c00876e439ec91f120405/astropy-7.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fd94f755f2e9104c5caf0afe69175d2620708828233d387c1dad0044e79f543", size = 10030192, upload-time = "2025-05-20T13:40:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/17/ce/f9e103e6be3d801cadffef8eb814bd10e4de3603b9ba9f97dd41c6a59bde/astropy-7.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49effea6de855dcdba09a211d51ca279766c66284c8db3d911b4523bbd96eae9", size = 10079889, upload-time = "2025-05-20T13:40:03.076Z" }, + { url = "https://files.pythonhosted.org/packages/a3/fe/605a09e74b03ea2981632cd9a9a3720bd8d232ddaa729fc63ce46d538218/astropy-7.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:52709e6bbe65c6d6c2a342506ad8c2801ff2aed80cc0a5d7817387122e0ac616", size = 10084024, upload-time = "2025-05-20T13:40:04.776Z" }, + { url = "https://files.pythonhosted.org/packages/af/e9/8259c4699227e5ee71d5d15a297f1e9469b99a980a1fd3140ecf68e98c4d/astropy-7.1.0-cp313-cp313-win32.whl", hash = "sha256:a77d07a104ec24ed7f8a45de3935f46071551aa2eb6d6b62efebdc888f65b6d6", size = 6150536, upload-time = "2025-05-20T13:40:06.638Z" }, + { url = "https://files.pythonhosted.org/packages/0b/dd/d9c55247172f7156696d85c9146b64b41c30405bf86b775a731bed4d52f8/astropy-7.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e317f34e33a8f5517bc9fc6fbc005f42730d3be7d2820ef41e0468bcb796843", size = 6278127, upload-time = "2025-05-20T13:40:08.643Z" }, ] [[package]] name = "astropy-iers-data" -version = "0.2026.4.27.1.3.2" +version = "0.2025.9.15.0.37.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/e5/dc5474840c8b4ccf73db3475f88d9e40fcffa728f23e87707afa3460b30c/astropy_iers_data-0.2026.4.27.1.3.2.tar.gz", hash = "sha256:fc71b5b2e601afb1b8c4f22a35161c551d67469ec65502123591dae6a87d453b", size = 1931758, upload-time = "2026-04-27T01:03:45.375Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/97/247deecc933ca6a30ed137c19740d3f738df27fdc1de9c0a88cfaba00779/astropy_iers_data-0.2025.9.15.0.37.0.tar.gz", hash = "sha256:33e513dcfdc1b5a7c34e1cc674f4a050b29f93af5ac829e05d868a681d3091d8", size = 1908647, upload-time = "2025-09-15T00:37:46.311Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/14/1c50311a2873e8c89f0b1d0eda65cf572b360917b14b70ba9d0678fc6acc/astropy_iers_data-0.2026.4.27.1.3.2-py3-none-any.whl", hash = "sha256:3c09006b1b7c369a4dd9ba7e395b04cbfedb41a9253013b3bf7e5b8ac53a7699", size = 1988568, upload-time = "2026-04-27T01:03:43.278Z" }, + { url = "https://files.pythonhosted.org/packages/03/8c/a556b71e4603d6156814da968b7cefa9bd882e59dc691168584418f4de77/astropy_iers_data-0.2025.9.15.0.37.0-py3-none-any.whl", hash = "sha256:4ea19813150f2d6dfb0257c5e96d14ae9eac6e5a80af27ec79e3aaef8aadfa93", size = 1964242, upload-time = "2025-09-15T00:37:44.298Z" }, ] [[package]] @@ -137,11 +144,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.4.22" +version = "2025.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, ] [[package]] @@ -183,75 +190,44 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, - { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, - { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, - { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, - { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, - { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, - { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, - { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, - { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, - { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, - { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, - { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, - { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, - { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, - { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, - { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, - { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, + { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, + { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, + { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, ] [[package]] @@ -265,46 +241,46 @@ wheels = [ [[package]] name = "cryptography" -version = "47.0.0" +version = "46.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/34/c6/2733531243fba725f58611b918056b277692f1033373dcc8bd01af1c05d4/cryptography-47.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973", size = 4644617, upload-time = "2026-04-24T19:53:06.909Z" }, - { url = "https://files.pythonhosted.org/packages/00/e3/b27be1a670a9b87f855d211cf0e1174a5d721216b7616bd52d8581d912ed/cryptography-47.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8", size = 4668186, upload-time = "2026-04-24T19:53:09.053Z" }, - { url = "https://files.pythonhosted.org/packages/81/b9/8443cfe5d17d482d348cee7048acf502bb89a51b6382f06240fd290d4ca3/cryptography-47.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b", size = 4651244, upload-time = "2026-04-24T19:53:11.217Z" }, - { url = "https://files.pythonhosted.org/packages/5d/5e/13ed0cdd0eb88ba159d6dd5ebfece8cb901dbcf1ae5ac4072e28b55d3153/cryptography-47.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:34b4358b925a5ea3e14384ca781a2c0ef7ac219b57bb9eacc4457078e2b19f92", size = 5252906, upload-time = "2026-04-24T19:53:13.532Z" }, - { url = "https://files.pythonhosted.org/packages/64/16/ed058e1df0f33d440217cd120d41d5dda9dd215a80b8187f68483185af82/cryptography-47.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7", size = 4701842, upload-time = "2026-04-24T19:53:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/02/e0/3d30986b30fdbd9e969abbdf8ba00ed0618615144341faeb57f395a084fe/cryptography-47.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:1e47422b5557bb82d3fff997e8d92cff4e28b9789576984f08c248d2b3535d93", size = 4289313, upload-time = "2026-04-24T19:53:17.755Z" }, - { url = "https://files.pythonhosted.org/packages/df/fd/32db38e3ad0cb331f0691cb4c7a8a6f176f679124dee746b3af6633db4d9/cryptography-47.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6f29f36582e6151d9686235e586dd35bb67491f024767d10b842e520dc6a07ac", size = 4650964, upload-time = "2026-04-24T19:53:20.062Z" }, - { url = "https://files.pythonhosted.org/packages/86/53/5395d944dfd48cb1f67917f533c609c34347185ef15eb4308024c876f274/cryptography-47.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a9b761f012a943b7de0e828843c5688d0de94a0578d44d6c85a1bae32f87791f", size = 5207817, upload-time = "2026-04-24T19:53:22.498Z" }, - { url = "https://files.pythonhosted.org/packages/34/4f/e5711b28e1901f7d480a2b1b688b645aa4c77c73f10731ed17e7f7db3f0d/cryptography-47.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8", size = 4701544, upload-time = "2026-04-24T19:53:24.356Z" }, - { url = "https://files.pythonhosted.org/packages/22/22/c8ddc25de3010fc8da447648f5a092c40e7a8fadf01dd6d255d9c0b9373d/cryptography-47.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318", size = 4783536, upload-time = "2026-04-24T19:53:26.665Z" }, - { url = "https://files.pythonhosted.org/packages/66/b6/d4a68f4ea999c6d89e8498579cba1c5fcba4276284de7773b17e4fa69293/cryptography-47.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001", size = 4926106, upload-time = "2026-04-24T19:53:28.686Z" }, - { url = "https://files.pythonhosted.org/packages/07/55/c18f75724544872f234678fdedc871391722cb34a2aee19faa9f63100bb2/cryptography-47.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7", size = 4631180, upload-time = "2026-04-24T19:53:37.517Z" }, - { url = "https://files.pythonhosted.org/packages/ee/65/31a5cc0eaca99cec5bafffe155d407115d96136bb161e8b49e0ef73f09a7/cryptography-47.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1", size = 4653529, upload-time = "2026-04-24T19:53:39.775Z" }, - { url = "https://files.pythonhosted.org/packages/e5/bc/641c0519a495f3bfd0421b48d7cd325c4336578523ccd76ea322b6c29c7a/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c", size = 4638570, upload-time = "2026-04-24T19:53:42.129Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f2/300327b0a47f6dc94dd8b71b57052aefe178bb51745073d73d80604f11ab/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3fb8fa48075fad7193f2e5496135c6a76ac4b2aa5a38433df0a539296b377829", size = 5238019, upload-time = "2026-04-24T19:53:44.577Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5a/5b5cf994391d4bf9d9c7efd4c66aabe4d95227256627f8fea6cff7dfadbd/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11438c7518132d95f354fa01a4aa2f806d172a061a7bed18cf18cbdacdb204d7", size = 4686832, upload-time = "2026-04-24T19:53:47.015Z" }, - { url = "https://files.pythonhosted.org/packages/dc/2c/ae950e28fd6475c852fc21a44db3e6b5bcc1261d1e370f2b6e42fa800fef/cryptography-47.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8c1a736bbb3288005796c3f7ccb9453360d7fed483b13b9f468aea5171432923", size = 4269301, upload-time = "2026-04-24T19:53:48.97Z" }, - { url = "https://files.pythonhosted.org/packages/67/fb/6a39782e150ffe5cc1b0018cb6ddc48bf7ca62b498d7539ffc8a758e977d/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:f1557695e5c2b86e204f6ce9470497848634100787935ab7adc5397c54abd7ab", size = 4638110, upload-time = "2026-04-24T19:53:51.011Z" }, - { url = "https://files.pythonhosted.org/packages/8e/d7/0b3c71090a76e5c203164a47688b697635ece006dcd2499ab3a4dbd3f0bd/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:f9a034b642b960767fb343766ae5ba6ad653f2e890ddd82955aef288ffea8736", size = 5194988, upload-time = "2026-04-24T19:53:52.962Z" }, - { url = "https://files.pythonhosted.org/packages/63/33/63a961498a9df51721ab578c5a2622661411fc520e00bd83b0cc64eb20c4/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7", size = 4686563, upload-time = "2026-04-24T19:53:55.274Z" }, - { url = "https://files.pythonhosted.org/packages/b7/bf/5ee5b145248f92250de86145d1c1d6edebbd57a7fe7caa4dedb5d4cf06a1/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52", size = 4770094, upload-time = "2026-04-24T19:53:57.753Z" }, - { url = "https://files.pythonhosted.org/packages/92/43/21d220b2da5d517773894dacdcdb5c682c28d3fffce65548cb06e87d5501/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd", size = 4913811, upload-time = "2026-04-24T19:54:00.236Z" }, - { url = "https://files.pythonhosted.org/packages/01/64/d7b1e54fdb69f22d24a64bb3e88dc718b31c7fb10ef0b9691a3cf7eeea6e/cryptography-47.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27", size = 4635767, upload-time = "2026-04-24T19:54:08.519Z" }, - { url = "https://files.pythonhosted.org/packages/8b/7b/cca826391fb2a94efdcdfe4631eb69306ee1cff0b22f664a412c90713877/cryptography-47.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10", size = 4654350, upload-time = "2026-04-24T19:54:10.795Z" }, - { url = "https://files.pythonhosted.org/packages/4c/65/4b57bcc823f42a991627c51c2f68c9fd6eb1393c1756aac876cba2accae2/cryptography-47.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b", size = 4643394, upload-time = "2026-04-24T19:54:13.275Z" }, - { url = "https://files.pythonhosted.org/packages/f4/c4/2c5fbeea70adbbca2bbae865e1d605d6a4a7f8dbd9d33eaf69645087f06c/cryptography-47.0.0-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9af828c0d5a65c70ec729cd7495a4bf1a67ecb66417b8f02ff125ab8a6326a74", size = 5225777, upload-time = "2026-04-24T19:54:15.18Z" }, - { url = "https://files.pythonhosted.org/packages/7e/b8/ac57107ef32749d2b244e36069bb688792a363aaaa3acc9e3cf84c130315/cryptography-47.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:256d07c78a04d6b276f5df935a9923275f53bd1522f214447fdf365494e2d515", size = 4688771, upload-time = "2026-04-24T19:54:17.835Z" }, - { url = "https://files.pythonhosted.org/packages/56/fc/9f1de22ff8be99d991f240a46863c52d475404c408886c5a38d2b5c3bb26/cryptography-47.0.0-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:5d0e362ff51041b0c0d219cc7d6924d7b8996f57ce5712bdcef71eb3c65a59cc", size = 4270753, upload-time = "2026-04-24T19:54:19.963Z" }, - { url = "https://files.pythonhosted.org/packages/00/68/d70c852797aa68e8e48d12e5a87170c43f67bb4a59403627259dd57d15de/cryptography-47.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1581aef4219f7ca2849d0250edaa3866212fb74bf5667284f46aa92f9e65c1ca", size = 4642911, upload-time = "2026-04-24T19:54:21.818Z" }, - { url = "https://files.pythonhosted.org/packages/a5/51/661cbee74f594c5d97ff82d34f10d5551c085ca4668645f4606ebd22bd5d/cryptography-47.0.0-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a49a3eb5341b9503fa3000a9a0db033161db90d47285291f53c2a9d2cd1b7f76", size = 5181411, upload-time = "2026-04-24T19:54:24.376Z" }, - { url = "https://files.pythonhosted.org/packages/94/87/f2b6c374a82cf076cfa1416992ac8e8ec94d79facc37aec87c1a5cb72352/cryptography-47.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe", size = 4688262, upload-time = "2026-04-24T19:54:26.946Z" }, - { url = "https://files.pythonhosted.org/packages/14/e2/8b7462f4acf21ec509616f0245018bb197194ab0b65c2ea21a0bdd53c0eb/cryptography-47.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31", size = 4775506, upload-time = "2026-04-24T19:54:28.926Z" }, - { url = "https://files.pythonhosted.org/packages/70/75/158e494e4c08dc05e039da5bb48553826bd26c23930cf8d3cd5f21fa8921/cryptography-47.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7", size = 4912060, upload-time = "2026-04-24T19:54:30.869Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/a9/62/e3664e6ffd7743e1694b244dde70b43a394f6f7fbcacf7014a8ff5197c73/cryptography-46.0.1.tar.gz", hash = "sha256:ed570874e88f213437f5cf758f9ef26cbfc3f336d889b1e592ee11283bb8d1c7", size = 749198, upload-time = "2025-09-17T00:10:35.797Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/59/9ae689a25047e0601adfcb159ec4f83c0b4149fdb5c3030cc94cd218141d/cryptography-46.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ff483716be32690c14636e54a1f6e2e1b7bf8e22ca50b989f88fa1b2d287080", size = 4308182, upload-time = "2025-09-17T00:08:39.388Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/ca6cc9df7118f2fcd142c76b1da0f14340d77518c05b1ebfbbabca6b9e7d/cryptography-46.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9873bf7c1f2a6330bdfe8621e7ce64b725784f9f0c3a6a55c3047af5849f920e", size = 4572393, upload-time = "2025-09-17T00:08:41.663Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a3/0f5296f63815d8e985922b05c31f77ce44787b3127a67c0b7f70f115c45f/cryptography-46.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0dfb7c88d4462a0cfdd0d87a3c245a7bc3feb59de101f6ff88194f740f72eda6", size = 4308400, upload-time = "2025-09-17T00:08:43.559Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8c/74fcda3e4e01be1d32775d5b4dd841acaac3c1b8fa4d0774c7ac8d52463d/cryptography-46.0.1-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e22801b61613ebdebf7deb18b507919e107547a1d39a3b57f5f855032dd7cfb8", size = 4015786, upload-time = "2025-09-17T00:08:45.758Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b8/85d23287baeef273b0834481a3dd55bbed3a53587e3b8d9f0898235b8f91/cryptography-46.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:757af4f6341ce7a1e47c326ca2a81f41d236070217e5fbbad61bbfe299d55d28", size = 4982606, upload-time = "2025-09-17T00:08:47.602Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/de61ad5b52433b389afca0bc70f02a7a1f074651221f599ce368da0fe437/cryptography-46.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f7a24ea78de345cfa7f6a8d3bde8b242c7fac27f2bd78fa23474ca38dfaeeab9", size = 4604234, upload-time = "2025-09-17T00:08:49.879Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1f/dbd4d6570d84748439237a7478d124ee0134bf166ad129267b7ed8ea6d22/cryptography-46.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e8776dac9e660c22241b6587fae51a67b4b0147daa4d176b172c3ff768ad736", size = 4307669, upload-time = "2025-09-17T00:08:52.321Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fd/ca0a14ce7f0bfe92fa727aacaf2217eb25eb7e4ed513b14d8e03b26e63ed/cryptography-46.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9f40642a140c0c8649987027867242b801486865277cbabc8c6059ddef16dc8b", size = 4947579, upload-time = "2025-09-17T00:08:54.697Z" }, + { url = "https://files.pythonhosted.org/packages/89/6b/09c30543bb93401f6f88fce556b3bdbb21e55ae14912c04b7bf355f5f96c/cryptography-46.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:449ef2b321bec7d97ef2c944173275ebdab78f3abdd005400cc409e27cd159ab", size = 4603669, upload-time = "2025-09-17T00:08:57.16Z" }, + { url = "https://files.pythonhosted.org/packages/23/9a/38cb01cb09ce0adceda9fc627c9cf98eb890fc8d50cacbe79b011df20f8a/cryptography-46.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2dd339ba3345b908fa3141ddba4025568fa6fd398eabce3ef72a29ac2d73ad75", size = 4435828, upload-time = "2025-09-17T00:08:59.606Z" }, + { url = "https://files.pythonhosted.org/packages/0f/53/435b5c36a78d06ae0bef96d666209b0ecd8f8181bfe4dda46536705df59e/cryptography-46.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7411c910fb2a412053cf33cfad0153ee20d27e256c6c3f14d7d7d1d9fec59fd5", size = 4709553, upload-time = "2025-09-17T00:09:01.832Z" }, + { url = "https://files.pythonhosted.org/packages/26/34/0ff0bb2d2c79f25a2a63109f3b76b9108a906dd2a2eb5c1d460b9938adbb/cryptography-46.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9babb7818fdd71394e576cf26c5452df77a355eac1a27ddfa24096665a27f8fd", size = 4293515, upload-time = "2025-09-17T00:09:12.861Z" }, + { url = "https://files.pythonhosted.org/packages/df/b7/d4f848aee24ecd1be01db6c42c4a270069a4f02a105d9c57e143daf6cf0f/cryptography-46.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9f2c4cc63be3ef43c0221861177cee5d14b505cd4d4599a89e2cd273c4d3542a", size = 4545619, upload-time = "2025-09-17T00:09:15.397Z" }, + { url = "https://files.pythonhosted.org/packages/44/a5/42fedefc754fd1901e2d95a69815ea4ec8a9eed31f4c4361fcab80288661/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:41c281a74df173876da1dc9a9b6953d387f06e3d3ed9284e3baae3ab3f40883a", size = 4299160, upload-time = "2025-09-17T00:09:17.155Z" }, + { url = "https://files.pythonhosted.org/packages/86/a1/cd21174f56e769c831fbbd6399a1b7519b0ff6280acec1b826d7b072640c/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0a17377fa52563d730248ba1f68185461fff36e8bc75d8787a7dd2e20a802b7a", size = 3994491, upload-time = "2025-09-17T00:09:18.971Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/a8cbfa1c029987ddc746fd966711d4fa71efc891d37fbe9f030fe5ab4eec/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:0d1922d9280e08cde90b518a10cd66831f632960a8d08cb3418922d83fce6f12", size = 4960157, upload-time = "2025-09-17T00:09:20.923Z" }, + { url = "https://files.pythonhosted.org/packages/67/ae/63a84e6789e0d5a2502edf06b552bcb0fa9ff16147265d5c44a211942abe/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:af84e8e99f1a82cea149e253014ea9dc89f75b82c87bb6c7242203186f465129", size = 4577263, upload-time = "2025-09-17T00:09:23.356Z" }, + { url = "https://files.pythonhosted.org/packages/ef/8f/1b9fa8e92bd9cbcb3b7e1e593a5232f2c1e6f9bd72b919c1a6b37d315f92/cryptography-46.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ef648d2c690703501714588b2ba640facd50fd16548133b11b2859e8655a69da", size = 4298703, upload-time = "2025-09-17T00:09:25.566Z" }, + { url = "https://files.pythonhosted.org/packages/c3/af/bb95db070e73fea3fae31d8a69ac1463d89d1c084220f549b00dd01094a8/cryptography-46.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:e94eb5fa32a8a9f9bf991f424f002913e3dd7c699ef552db9b14ba6a76a6313b", size = 4926363, upload-time = "2025-09-17T00:09:27.451Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3b/d8fb17ffeb3a83157a1cc0aa5c60691d062aceecba09c2e5e77ebfc1870c/cryptography-46.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:534b96c0831855e29fc3b069b085fd185aa5353033631a585d5cd4dd5d40d657", size = 4576958, upload-time = "2025-09-17T00:09:29.924Z" }, + { url = "https://files.pythonhosted.org/packages/d9/46/86bc3a05c10c8aa88c8ae7e953a8b4e407c57823ed201dbcba55c4d655f4/cryptography-46.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9b55038b5c6c47559aa33626d8ecd092f354e23de3c6975e4bb205df128a2a0", size = 4422507, upload-time = "2025-09-17T00:09:32.222Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4e/387e5a21dfd2b4198e74968a541cfd6128f66f8ec94ed971776e15091ac3/cryptography-46.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ec13b7105117dbc9afd023300fb9954d72ca855c274fe563e72428ece10191c0", size = 4683964, upload-time = "2025-09-17T00:09:34.118Z" }, + { url = "https://files.pythonhosted.org/packages/56/3e/13ce6eab9ad6eba1b15a7bd476f005a4c1b3f299f4c2f32b22408b0edccf/cryptography-46.0.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9ed64e5083fa806709e74fc5ea067dfef9090e5b7a2320a49be3c9df3583a2d8", size = 4301110, upload-time = "2025-09-17T00:09:45.614Z" }, + { url = "https://files.pythonhosted.org/packages/a2/67/65dc233c1ddd688073cf7b136b06ff4b84bf517ba5529607c9d79720fc67/cryptography-46.0.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:341fb7a26bc9d6093c1b124b9f13acc283d2d51da440b98b55ab3f79f2522ead", size = 4562369, upload-time = "2025-09-17T00:09:47.601Z" }, + { url = "https://files.pythonhosted.org/packages/17/db/d64ae4c6f4e98c3dac5bf35dd4d103f4c7c345703e43560113e5e8e31b2b/cryptography-46.0.1-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6ef1488967e729948d424d09c94753d0167ce59afba8d0f6c07a22b629c557b2", size = 4302126, upload-time = "2025-09-17T00:09:49.335Z" }, + { url = "https://files.pythonhosted.org/packages/3d/19/5f1eea17d4805ebdc2e685b7b02800c4f63f3dd46cfa8d4c18373fea46c8/cryptography-46.0.1-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7823bc7cdf0b747ecfb096d004cc41573c2f5c7e3a29861603a2871b43d3ef32", size = 4009431, upload-time = "2025-09-17T00:09:51.239Z" }, + { url = "https://files.pythonhosted.org/packages/81/b5/229ba6088fe7abccbfe4c5edb96c7a5ad547fac5fdd0d40aa6ea540b2985/cryptography-46.0.1-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:f736ab8036796f5a119ff8211deda416f8c15ce03776db704a7a4e17381cb2ef", size = 4980739, upload-time = "2025-09-17T00:09:54.181Z" }, + { url = "https://files.pythonhosted.org/packages/3a/9c/50aa38907b201e74bc43c572f9603fa82b58e831bd13c245613a23cff736/cryptography-46.0.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e46710a240a41d594953012213ea8ca398cd2448fbc5d0f1be8160b5511104a0", size = 4592289, upload-time = "2025-09-17T00:09:56.731Z" }, + { url = "https://files.pythonhosted.org/packages/5a/33/229858f8a5bb22f82468bb285e9f4c44a31978d5f5830bb4ea1cf8a4e454/cryptography-46.0.1-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:84ef1f145de5aee82ea2447224dc23f065ff4cc5791bb3b506615957a6ba8128", size = 4301815, upload-time = "2025-09-17T00:09:58.548Z" }, + { url = "https://files.pythonhosted.org/packages/52/cb/b76b2c87fbd6ed4a231884bea3ce073406ba8e2dae9defad910d33cbf408/cryptography-46.0.1-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9394c7d5a7565ac5f7d9ba38b2617448eba384d7b107b262d63890079fad77ca", size = 4943251, upload-time = "2025-09-17T00:10:00.475Z" }, + { url = "https://files.pythonhosted.org/packages/94/0f/f66125ecf88e4cb5b8017ff43f3a87ede2d064cb54a1c5893f9da9d65093/cryptography-46.0.1-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ed957044e368ed295257ae3d212b95456bd9756df490e1ac4538857f67531fcc", size = 4591247, upload-time = "2025-09-17T00:10:02.874Z" }, + { url = "https://files.pythonhosted.org/packages/f6/22/9f3134ae436b63b463cfdf0ff506a0570da6873adb4bf8c19b8a5b4bac64/cryptography-46.0.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f7de12fa0eee6234de9a9ce0ffcfa6ce97361db7a50b09b65c63ac58e5f22fc7", size = 4428534, upload-time = "2025-09-17T00:10:04.994Z" }, + { url = "https://files.pythonhosted.org/packages/89/39/e6042bcb2638650b0005c752c38ea830cbfbcbb1830e4d64d530000aa8dc/cryptography-46.0.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7fab1187b6c6b2f11a326f33b036f7168f5b996aedd0c059f9738915e4e8f53a", size = 4699541, upload-time = "2025-09-17T00:10:06.925Z" }, ] [[package]] @@ -355,20 +331,20 @@ wheels = [ [[package]] name = "idna" -version = "3.13" +version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "iniconfig" -version = "2.3.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] @@ -385,23 +361,23 @@ wheels = [ [[package]] name = "jaraco-context" -version = "6.1.2" +version = "6.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912, upload-time = "2024-08-20T03:39:27.358Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, + { url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825, upload-time = "2024-08-20T03:39:25.966Z" }, ] [[package]] name = "jaraco-functools" -version = "4.4.0" +version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/ed/1aa2d585304ec07262e1a83a9889880701079dde796ac7b1d1826f40c63d/jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294", size = 19755, upload-time = "2025-08-18T20:05:09.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, + { url = "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", size = 10408, upload-time = "2025-08-18T20:05:08.69Z" }, ] [[package]] @@ -427,7 +403,7 @@ wheels = [ [[package]] name = "keyring" -version = "25.7.0" +version = "25.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jaraco-classes" }, @@ -437,89 +413,71 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750, upload-time = "2024-12-25T15:26:45.782Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, + { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" }, ] [[package]] name = "lxml" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/d4/9326838b59dc36dfae42eec9656b97520f9997eee1de47b8316aaeed169c/lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", size = 8570663, upload-time = "2026-04-18T04:27:48.253Z" }, - { url = "https://files.pythonhosted.org/packages/d8/a4/053745ce1f8303ccbb788b86c0db3a91b973675cefc42566a188637b7c40/lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93", size = 4624024, upload-time = "2026-04-18T04:27:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/90/97/a517944b20f8fd0932ad2109482bee4e29fe721416387a363306667941f6/lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", size = 4930895, upload-time = "2026-04-18T04:32:56.29Z" }, - { url = "https://files.pythonhosted.org/packages/94/7c/e08a970727d556caa040a44773c7b7e3ad0f0d73dedc863543e9a8b931f2/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a", size = 5093820, upload-time = "2026-04-18T04:32:58.94Z" }, - { url = "https://files.pythonhosted.org/packages/88/ee/2a5c2aa2c32016a226ca25d3e1056a8102ea6e1fe308bf50213586635400/lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105", size = 5005790, upload-time = "2026-04-18T04:33:01.272Z" }, - { url = "https://files.pythonhosted.org/packages/e3/38/a0db9be8f38ad6043ab9429487c128dd1d30f07956ef43040402f8da49e8/lxml-6.1.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485", size = 5630827, upload-time = "2026-04-18T04:33:04.036Z" }, - { url = "https://files.pythonhosted.org/packages/31/ba/3c13d3fc24b7cacf675f808a3a1baabf43a30d0cd24c98f94548e9aa58eb/lxml-6.1.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814", size = 5240445, upload-time = "2026-04-18T04:33:06.87Z" }, - { url = "https://files.pythonhosted.org/packages/55/ba/eeef4ccba09b2212fe239f46c1692a98db1878e0872ae320756488878a94/lxml-6.1.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32", size = 5350121, upload-time = "2026-04-18T04:33:09.365Z" }, - { url = "https://files.pythonhosted.org/packages/7e/01/1da87c7b587c38d0cbe77a01aae3b9c1c49ed47d76918ef3db8fc151b1ca/lxml-6.1.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad", size = 4694949, upload-time = "2026-04-18T04:33:11.628Z" }, - { url = "https://files.pythonhosted.org/packages/a1/88/7db0fe66d5aaf128443ee1623dec3db1576f3e4c17751ec0ef5866468590/lxml-6.1.0-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54", size = 5243901, upload-time = "2026-04-18T04:33:13.95Z" }, - { url = "https://files.pythonhosted.org/packages/00/a8/1346726af7d1f6fca1f11223ba34001462b0a3660416986d37641708d57c/lxml-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d", size = 5048054, upload-time = "2026-04-18T04:33:16.965Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b7/85057012f035d1a0c87e02f8c723ca3c3e6e0728bcf4cb62080b21b1c1e3/lxml-6.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69", size = 4777324, upload-time = "2026-04-18T04:33:19.832Z" }, - { url = "https://files.pythonhosted.org/packages/75/6c/ad2f94a91073ef570f33718040e8e160d5fb93331cf1ab3ca1323f939e2d/lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d", size = 5645702, upload-time = "2026-04-18T04:33:22.436Z" }, - { url = "https://files.pythonhosted.org/packages/3b/89/0bb6c0bd549c19004c60eea9dc554dd78fd647b72314ef25d460e0d208c6/lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5", size = 5232901, upload-time = "2026-04-18T04:33:26.21Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d9/d609a11fb567da9399f525193e2b49847b5a409cdebe737f06a8b7126bdc/lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d", size = 5261333, upload-time = "2026-04-18T04:33:28.984Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3a/ac3f99ec8ac93089e7dd556f279e0d14c24de0a74a507e143a2e4b496e7c/lxml-6.1.0-cp312-cp312-win32.whl", hash = "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f", size = 3596289, upload-time = "2026-04-18T04:27:42.819Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a7/0a915557538593cb1bbeedcd40e13c7a261822c26fecbbdb71dad0c2f540/lxml-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366", size = 3997059, upload-time = "2026-04-18T04:27:46.764Z" }, - { url = "https://files.pythonhosted.org/packages/92/96/a5dc078cf0126fbfbc35611d77ecd5da80054b5893e28fb213a5613b9e1d/lxml-6.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819", size = 3659552, upload-time = "2026-04-18T04:27:51.133Z" }, - { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, - { url = "https://files.pythonhosted.org/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" }, - { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, - { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, - { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, - { url = "https://files.pythonhosted.org/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" }, - { url = "https://files.pythonhosted.org/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578, upload-time = "2026-04-18T04:33:44.596Z" }, - { url = "https://files.pythonhosted.org/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" }, - { url = "https://files.pythonhosted.org/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" }, - { url = "https://files.pythonhosted.org/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" }, - { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, - { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, - { url = "https://files.pythonhosted.org/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" }, - { url = "https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" }, - { url = "https://files.pythonhosted.org/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" }, - { url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" }, - { url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" }, - { url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" }, - { url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522, upload-time = "2026-04-18T04:34:14.89Z" }, - { url = "https://files.pythonhosted.org/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310, upload-time = "2026-04-18T04:34:17.652Z" }, - { url = "https://files.pythonhosted.org/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799, upload-time = "2026-04-18T04:34:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693, upload-time = "2026-04-18T04:34:23.541Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708, upload-time = "2026-04-18T04:34:26.001Z" }, - { url = "https://files.pythonhosted.org/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737, upload-time = "2026-04-18T04:34:28.32Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817, upload-time = "2026-04-18T04:34:30.784Z" }, - { url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" }, - { url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" }, - { url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" }, - { url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" }, - { url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" }, - { url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" }, - { url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" }, - { url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" }, - { url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" }, - { url = "https://files.pythonhosted.org/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008, upload-time = "2026-04-18T04:34:51.503Z" }, - { url = "https://files.pythonhosted.org/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451, upload-time = "2026-04-18T04:34:54.263Z" }, - { url = "https://files.pythonhosted.org/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135, upload-time = "2026-04-18T04:34:56.818Z" }, - { url = "https://files.pythonhosted.org/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126, upload-time = "2026-04-18T04:34:59.704Z" }, - { url = "https://files.pythonhosted.org/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579, upload-time = "2026-04-18T04:35:02.658Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206, upload-time = "2026-04-18T04:35:05.175Z" }, - { url = "https://files.pythonhosted.org/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906, upload-time = "2026-04-18T04:35:08.098Z" }, - { url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" }, - { url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" }, - { url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" }, +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/bd/f9d01fd4132d81c6f43ab01983caea69ec9614b913c290a26738431a015d/lxml-6.0.1.tar.gz", hash = "sha256:2b3a882ebf27dd026df3801a87cf49ff791336e0f94b0fad195db77e01240690", size = 4070214, upload-time = "2025-08-22T10:37:53.525Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/a9/82b244c8198fcdf709532e39a1751943a36b3e800b420adc739d751e0299/lxml-6.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c03ac546adaabbe0b8e4a15d9ad815a281afc8d36249c246aecf1aaad7d6f200", size = 8422788, upload-time = "2025-08-22T10:32:56.612Z" }, + { url = "https://files.pythonhosted.org/packages/c9/8d/1ed2bc20281b0e7ed3e6c12b0a16e64ae2065d99be075be119ba88486e6d/lxml-6.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33b862c7e3bbeb4ba2c96f3a039f925c640eeba9087a4dc7a572ec0f19d89392", size = 4593547, upload-time = "2025-08-22T10:32:59.016Z" }, + { url = "https://files.pythonhosted.org/packages/76/53/d7fd3af95b72a3493bf7fbe842a01e339d8f41567805cecfecd5c71aa5ee/lxml-6.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a3ec1373f7d3f519de595032d4dcafae396c29407cfd5073f42d267ba32440d", size = 4948101, upload-time = "2025-08-22T10:33:00.765Z" }, + { url = "https://files.pythonhosted.org/packages/9d/51/4e57cba4d55273c400fb63aefa2f0d08d15eac021432571a7eeefee67bed/lxml-6.0.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03b12214fb1608f4cffa181ec3d046c72f7e77c345d06222144744c122ded870", size = 5108090, upload-time = "2025-08-22T10:33:03.108Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6e/5f290bc26fcc642bc32942e903e833472271614e24d64ad28aaec09d5dae/lxml-6.0.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:207ae0d5f0f03b30f95e649a6fa22aa73f5825667fee9c7ec6854d30e19f2ed8", size = 5021791, upload-time = "2025-08-22T10:33:06.972Z" }, + { url = "https://files.pythonhosted.org/packages/13/d4/2e7551a86992ece4f9a0f6eebd4fb7e312d30f1e372760e2109e721d4ce6/lxml-6.0.1-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:32297b09ed4b17f7b3f448de87a92fb31bb8747496623483788e9f27c98c0f00", size = 5358861, upload-time = "2025-08-22T10:33:08.967Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5f/cb49d727fc388bf5fd37247209bab0da11697ddc5e976ccac4826599939e/lxml-6.0.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e18224ea241b657a157c85e9cac82c2b113ec90876e01e1f127312006233756", size = 5652569, upload-time = "2025-08-22T10:33:10.815Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b8/66c1ef8c87ad0f958b0a23998851e610607c74849e75e83955d5641272e6/lxml-6.0.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a07a994d3c46cd4020c1ea566345cf6815af205b1e948213a4f0f1d392182072", size = 5252262, upload-time = "2025-08-22T10:33:12.673Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ef/131d3d6b9590e64fdbb932fbc576b81fcc686289da19c7cb796257310e82/lxml-6.0.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:2287fadaa12418a813b05095485c286c47ea58155930cfbd98c590d25770e225", size = 4710309, upload-time = "2025-08-22T10:33:14.952Z" }, + { url = "https://files.pythonhosted.org/packages/bc/3f/07f48ae422dce44902309aa7ed386c35310929dc592439c403ec16ef9137/lxml-6.0.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b4e597efca032ed99f418bd21314745522ab9fa95af33370dcee5533f7f70136", size = 5265786, upload-time = "2025-08-22T10:33:16.721Z" }, + { url = "https://files.pythonhosted.org/packages/11/c7/125315d7b14ab20d9155e8316f7d287a4956098f787c22d47560b74886c4/lxml-6.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9696d491f156226decdd95d9651c6786d43701e49f32bf23715c975539aa2b3b", size = 5062272, upload-time = "2025-08-22T10:33:18.478Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c3/51143c3a5fc5168a7c3ee626418468ff20d30f5a59597e7b156c1e61fba8/lxml-6.0.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e4e3cd3585f3c6f87cdea44cda68e692cc42a012f0131d25957ba4ce755241a7", size = 4786955, upload-time = "2025-08-22T10:33:20.34Z" }, + { url = "https://files.pythonhosted.org/packages/11/86/73102370a420ec4529647b31c4a8ce8c740c77af3a5fae7a7643212d6f6e/lxml-6.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:45cbc92f9d22c28cd3b97f8d07fcefa42e569fbd587dfdac76852b16a4924277", size = 5673557, upload-time = "2025-08-22T10:33:22.282Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2d/aad90afaec51029aef26ef773b8fd74a9e8706e5e2f46a57acd11a421c02/lxml-6.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f8c9bcfd2e12299a442fba94459adf0b0d001dbc68f1594439bfa10ad1ecb74b", size = 5254211, upload-time = "2025-08-22T10:33:24.15Z" }, + { url = "https://files.pythonhosted.org/packages/63/01/c9e42c8c2d8b41f4bdefa42ab05448852e439045f112903dd901b8fbea4d/lxml-6.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1e9dc2b9f1586e7cd77753eae81f8d76220eed9b768f337dc83a3f675f2f0cf9", size = 5275817, upload-time = "2025-08-22T10:33:26.007Z" }, + { url = "https://files.pythonhosted.org/packages/bc/1f/962ea2696759abe331c3b0e838bb17e92224f39c638c2068bf0d8345e913/lxml-6.0.1-cp312-cp312-win32.whl", hash = "sha256:987ad5c3941c64031f59c226167f55a04d1272e76b241bfafc968bdb778e07fb", size = 3610889, upload-time = "2025-08-22T10:33:28.169Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/22c86a990b51b44442b75c43ecb2f77b8daba8c4ba63696921966eac7022/lxml-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:abb05a45394fd76bf4a60c1b7bec0e6d4e8dfc569fc0e0b1f634cd983a006ddc", size = 4010925, upload-time = "2025-08-22T10:33:29.874Z" }, + { url = "https://files.pythonhosted.org/packages/b2/21/dc0c73325e5eb94ef9c9d60dbb5dcdcb2e7114901ea9509735614a74e75a/lxml-6.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:c4be29bce35020d8579d60aa0a4e95effd66fcfce31c46ffddf7e5422f73a299", size = 3671922, upload-time = "2025-08-22T10:33:31.535Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/cd757eeec4548e6652eff50b944079d18ce5f8182d2b2cf514e125e8fbcb/lxml-6.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:485eda5d81bb7358db96a83546949c5fe7474bec6c68ef3fa1fb61a584b00eea", size = 8405139, upload-time = "2025-08-22T10:33:34.09Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/0290bb86a7403893f5e9658490c705fcea103b9191f2039752b071b4ef07/lxml-6.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d12160adea318ce3d118f0b4fbdff7d1225c75fb7749429541b4d217b85c3f76", size = 4585954, upload-time = "2025-08-22T10:33:36.294Z" }, + { url = "https://files.pythonhosted.org/packages/88/a7/4bb54dd1e626342a0f7df6ec6ca44fdd5d0e100ace53acc00e9a689ead04/lxml-6.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48c8d335d8ab72f9265e7ba598ae5105a8272437403f4032107dbcb96d3f0b29", size = 4944052, upload-time = "2025-08-22T10:33:38.19Z" }, + { url = "https://files.pythonhosted.org/packages/71/8d/20f51cd07a7cbef6214675a8a5c62b2559a36d9303fe511645108887c458/lxml-6.0.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:405e7cf9dbdbb52722c231e0f1257214202dfa192327fab3de45fd62e0554082", size = 5098885, upload-time = "2025-08-22T10:33:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/63/efceeee7245d45f97d548e48132258a36244d3c13c6e3ddbd04db95ff496/lxml-6.0.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:299a790d403335a6a057ade46f92612ebab87b223e4e8c5308059f2dc36f45ed", size = 5017542, upload-time = "2025-08-22T10:33:41.896Z" }, + { url = "https://files.pythonhosted.org/packages/57/5d/92cb3d3499f5caba17f7933e6be3b6c7de767b715081863337ced42eb5f2/lxml-6.0.1-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:48da704672f6f9c461e9a73250440c647638cc6ff9567ead4c3b1f189a604ee8", size = 5347303, upload-time = "2025-08-22T10:33:43.868Z" }, + { url = "https://files.pythonhosted.org/packages/69/f8/606fa16a05d7ef5e916c6481c634f40870db605caffed9d08b1a4fb6b989/lxml-6.0.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:21e364e1bb731489e3f4d51db416f991a5d5da5d88184728d80ecfb0904b1d68", size = 5641055, upload-time = "2025-08-22T10:33:45.784Z" }, + { url = "https://files.pythonhosted.org/packages/b3/01/15d5fc74ebb49eac4e5df031fbc50713dcc081f4e0068ed963a510b7d457/lxml-6.0.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bce45a2c32032afddbd84ed8ab092130649acb935536ef7a9559636ce7ffd4a", size = 5242719, upload-time = "2025-08-22T10:33:48.089Z" }, + { url = "https://files.pythonhosted.org/packages/42/a5/1b85e2aaaf8deaa67e04c33bddb41f8e73d07a077bf9db677cec7128bfb4/lxml-6.0.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:fa164387ff20ab0e575fa909b11b92ff1481e6876835014e70280769920c4433", size = 4717310, upload-time = "2025-08-22T10:33:49.852Z" }, + { url = "https://files.pythonhosted.org/packages/42/23/f3bb1292f55a725814317172eeb296615db3becac8f1a059b53c51fc1da8/lxml-6.0.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7587ac5e000e1594e62278422c5783b34a82b22f27688b1074d71376424b73e8", size = 5254024, upload-time = "2025-08-22T10:33:52.22Z" }, + { url = "https://files.pythonhosted.org/packages/b4/be/4d768f581ccd0386d424bac615d9002d805df7cc8482ae07d529f60a3c1e/lxml-6.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:57478424ac4c9170eabf540237125e8d30fad1940648924c058e7bc9fb9cf6dd", size = 5055335, upload-time = "2025-08-22T10:33:54.041Z" }, + { url = "https://files.pythonhosted.org/packages/40/07/ed61d1a3e77d1a9f856c4fab15ee5c09a2853fb7af13b866bb469a3a6d42/lxml-6.0.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:09c74afc7786c10dd6afaa0be2e4805866beadc18f1d843cf517a7851151b499", size = 4784864, upload-time = "2025-08-22T10:33:56.382Z" }, + { url = "https://files.pythonhosted.org/packages/01/37/77e7971212e5c38a55431744f79dff27fd751771775165caea096d055ca4/lxml-6.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7fd70681aeed83b196482d42a9b0dc5b13bab55668d09ad75ed26dff3be5a2f5", size = 5657173, upload-time = "2025-08-22T10:33:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/32/a3/e98806d483941cd9061cc838b1169626acef7b2807261fbe5e382fcef881/lxml-6.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:10a72e456319b030b3dd900df6b1f19d89adf06ebb688821636dc406788cf6ac", size = 5245896, upload-time = "2025-08-22T10:34:00.586Z" }, + { url = "https://files.pythonhosted.org/packages/07/de/9bb5a05e42e8623bf06b4638931ea8c8f5eb5a020fe31703abdbd2e83547/lxml-6.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b0fa45fb5f55111ce75b56c703843b36baaf65908f8b8d2fbbc0e249dbc127ed", size = 5267417, upload-time = "2025-08-22T10:34:02.719Z" }, + { url = "https://files.pythonhosted.org/packages/f2/43/c1cb2a7c67226266c463ef8a53b82d42607228beb763b5fbf4867e88a21f/lxml-6.0.1-cp313-cp313-win32.whl", hash = "sha256:01dab65641201e00c69338c9c2b8a0f2f484b6b3a22d10779bb417599fae32b5", size = 3610051, upload-time = "2025-08-22T10:34:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/34/96/6a6c3b8aa480639c1a0b9b6faf2a63fb73ab79ffcd2a91cf28745faa22de/lxml-6.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:bdf8f7c8502552d7bff9e4c98971910a0a59f60f88b5048f608d0a1a75e94d1c", size = 4009325, upload-time = "2025-08-22T10:34:06.24Z" }, + { url = "https://files.pythonhosted.org/packages/8c/66/622e8515121e1fd773e3738dae71b8df14b12006d9fb554ce90886689fd0/lxml-6.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a6aeca75959426b9fd8d4782c28723ba224fe07cfa9f26a141004210528dcbe2", size = 3670443, upload-time = "2025-08-22T10:34:07.974Z" }, + { url = "https://files.pythonhosted.org/packages/38/e3/b7eb612ce07abe766918a7e581ec6a0e5212352194001fd287c3ace945f0/lxml-6.0.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:29b0e849ec7030e3ecb6112564c9f7ad6881e3b2375dd4a0c486c5c1f3a33859", size = 8426160, upload-time = "2025-08-22T10:34:10.154Z" }, + { url = "https://files.pythonhosted.org/packages/35/8f/ab3639a33595cf284fe733c6526da2ca3afbc5fd7f244ae67f3303cec654/lxml-6.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:02a0f7e629f73cc0be598c8b0611bf28ec3b948c549578a26111b01307fd4051", size = 4589288, upload-time = "2025-08-22T10:34:12.972Z" }, + { url = "https://files.pythonhosted.org/packages/2c/65/819d54f2e94d5c4458c1db8c1ccac9d05230b27c1038937d3d788eb406f9/lxml-6.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:beab5e54de016e730875f612ba51e54c331e2fa6dc78ecf9a5415fc90d619348", size = 4964523, upload-time = "2025-08-22T10:34:15.474Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4a/d4a74ce942e60025cdaa883c5a4478921a99ce8607fc3130f1e349a83b28/lxml-6.0.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a08aefecd19ecc4ebf053c27789dd92c87821df2583a4337131cf181a1dffa", size = 5101108, upload-time = "2025-08-22T10:34:17.348Z" }, + { url = "https://files.pythonhosted.org/packages/cb/48/67f15461884074edd58af17b1827b983644d1fae83b3d909e9045a08b61e/lxml-6.0.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36c8fa7e177649470bc3dcf7eae6bee1e4984aaee496b9ccbf30e97ac4127fa2", size = 5053498, upload-time = "2025-08-22T10:34:19.232Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d4/ec1bf1614828a5492f4af0b6a9ee2eb3e92440aea3ac4fa158e5228b772b/lxml-6.0.1-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:5d08e0f1af6916267bb7eff21c09fa105620f07712424aaae09e8cb5dd4164d1", size = 5351057, upload-time = "2025-08-22T10:34:21.143Z" }, + { url = "https://files.pythonhosted.org/packages/65/2b/c85929dacac08821f2100cea3eb258ce5c8804a4e32b774f50ebd7592850/lxml-6.0.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9705cdfc05142f8c38c97a61bd3a29581ceceb973a014e302ee4a73cc6632476", size = 5671579, upload-time = "2025-08-22T10:34:23.528Z" }, + { url = "https://files.pythonhosted.org/packages/d0/36/cf544d75c269b9aad16752fd9f02d8e171c5a493ca225cb46bb7ba72868c/lxml-6.0.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74555e2da7c1636e30bff4e6e38d862a634cf020ffa591f1f63da96bf8b34772", size = 5250403, upload-time = "2025-08-22T10:34:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e8/83dbc946ee598fd75fdeae6151a725ddeaab39bb321354a9468d4c9f44f3/lxml-6.0.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:e38b5f94c5a2a5dadaddd50084098dfd005e5a2a56cd200aaf5e0a20e8941782", size = 4696712, upload-time = "2025-08-22T10:34:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/f4/72/889c633b47c06205743ba935f4d1f5aa4eb7f0325d701ed2b0540df1b004/lxml-6.0.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a5ec101a92ddacb4791977acfc86c1afd624c032974bfb6a21269d1083c9bc49", size = 5268177, upload-time = "2025-08-22T10:34:29.804Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/f42a21a1428479b66ea0da7bd13e370436aecaff0cfe93270c7e165bd2a4/lxml-6.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5c17e70c82fd777df586c12114bbe56e4e6f823a971814fd40dec9c0de518772", size = 5094648, upload-time = "2025-08-22T10:34:31.703Z" }, + { url = "https://files.pythonhosted.org/packages/51/b0/5f8c1e8890e2ee1c2053c2eadd1cb0e4b79e2304e2912385f6ca666f48b1/lxml-6.0.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:45fdd0415a0c3d91640b5d7a650a8f37410966a2e9afebb35979d06166fd010e", size = 4745220, upload-time = "2025-08-22T10:34:33.595Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f9/820b5125660dae489ca3a21a36d9da2e75dd6b5ffe922088f94bbff3b8a0/lxml-6.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d417eba28981e720a14fcb98f95e44e7a772fe25982e584db38e5d3b6ee02e79", size = 5692913, upload-time = "2025-08-22T10:34:35.482Z" }, + { url = "https://files.pythonhosted.org/packages/23/8e/a557fae9eec236618aecf9ff35fec18df41b6556d825f3ad6017d9f6e878/lxml-6.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8e5d116b9e59be7934febb12c41cce2038491ec8fdb743aeacaaf36d6e7597e4", size = 5259816, upload-time = "2025-08-22T10:34:37.482Z" }, + { url = "https://files.pythonhosted.org/packages/fa/fd/b266cfaab81d93a539040be699b5854dd24c84e523a1711ee5f615aa7000/lxml-6.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c238f0d0d40fdcb695c439fe5787fa69d40f45789326b3bb6ef0d61c4b588d6e", size = 5276162, upload-time = "2025-08-22T10:34:39.507Z" }, + { url = "https://files.pythonhosted.org/packages/25/6c/6f9610fbf1de002048e80585ea4719591921a0316a8565968737d9f125ca/lxml-6.0.1-cp314-cp314-win32.whl", hash = "sha256:537b6cf1c5ab88cfd159195d412edb3e434fee880f206cbe68dff9c40e17a68a", size = 3669595, upload-time = "2025-08-22T10:34:41.783Z" }, + { url = "https://files.pythonhosted.org/packages/72/a5/506775e3988677db24dc75a7b03e04038e0b3d114ccd4bccea4ce0116c15/lxml-6.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:911d0a2bb3ef3df55b3d97ab325a9ca7e438d5112c102b8495321105d25a441b", size = 4079818, upload-time = "2025-08-22T10:34:44.04Z" }, + { url = "https://files.pythonhosted.org/packages/0a/44/9613f300201b8700215856e5edd056d4e58dd23368699196b58877d4408b/lxml-6.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:2834377b0145a471a654d699bdb3a2155312de492142ef5a1d426af2c60a0a31", size = 3753901, upload-time = "2025-08-22T10:34:45.799Z" }, ] [[package]] @@ -533,157 +491,134 @@ wheels = [ [[package]] name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, ] [[package]] name = "more-itertools" -version = "11.0.2" +version = "10.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, ] [[package]] name = "numpy" -version = "2.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, - { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, - { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, - { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, - { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, - { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, - { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, - { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, - { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, - { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, - { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, - { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, - { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, - { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, - { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, - { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, - { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, - { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, - { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, - { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, - { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, - { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, - { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, - { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, - { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, - { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, - { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, - { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, - { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, - { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, - { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, - { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, - { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, - { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, - { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, - { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, - { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, - { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, - { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, - { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" }, + { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" }, + { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" }, + { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" }, + { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" }, + { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" }, + { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf", size = 20949588, upload-time = "2025-09-09T15:56:59.087Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7", size = 14177802, upload-time = "2025-09-09T15:57:01.73Z" }, + { url = "https://files.pythonhosted.org/packages/35/c7/477a83887f9de61f1203bad89cf208b7c19cc9fef0cebef65d5a1a0619f2/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6", size = 5106537, upload-time = "2025-09-09T15:57:03.765Z" }, + { url = "https://files.pythonhosted.org/packages/52/47/93b953bd5866a6f6986344d045a207d3f1cfbad99db29f534ea9cee5108c/numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7", size = 6640743, upload-time = "2025-09-09T15:57:07.921Z" }, + { url = "https://files.pythonhosted.org/packages/23/83/377f84aaeb800b64c0ef4de58b08769e782edcefa4fea712910b6f0afd3c/numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c", size = 14278881, upload-time = "2025-09-09T15:57:11.349Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93", size = 16636301, upload-time = "2025-09-09T15:57:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/1287924242eb4fa3f9b3a2c30400f2e17eb2707020d1c5e3086fe7330717/numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae", size = 16053645, upload-time = "2025-09-09T15:57:16.534Z" }, + { url = "https://files.pythonhosted.org/packages/e6/93/b3d47ed882027c35e94ac2320c37e452a549f582a5e801f2d34b56973c97/numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86", size = 18578179, upload-time = "2025-09-09T15:57:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/487a2bccbf7cc9d4bfc5f0f197761a5ef27ba870f1e3bbb9afc4bbe3fcc2/numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8", size = 6312250, upload-time = "2025-09-09T15:57:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf", size = 12783269, upload-time = "2025-09-09T15:57:23.034Z" }, + { url = "https://files.pythonhosted.org/packages/fa/75/67b8ca554bbeaaeb3fac2e8bce46967a5a06544c9108ec0cf5cece559b6c/numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5", size = 10195314, upload-time = "2025-09-09T15:57:25.045Z" }, + { url = "https://files.pythonhosted.org/packages/11/d0/0d1ddec56b162042ddfafeeb293bac672de9b0cfd688383590090963720a/numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc", size = 21048025, upload-time = "2025-09-09T15:57:27.257Z" }, + { url = "https://files.pythonhosted.org/packages/36/9e/1996ca6b6d00415b6acbdd3c42f7f03ea256e2c3f158f80bd7436a8a19f3/numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc", size = 14301053, upload-time = "2025-09-09T15:57:30.077Z" }, + { url = "https://files.pythonhosted.org/packages/05/24/43da09aa764c68694b76e84b3d3f0c44cb7c18cdc1ba80e48b0ac1d2cd39/numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b", size = 5229444, upload-time = "2025-09-09T15:57:32.733Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/50ffb0f22f7218ef8af28dd089f79f68289a7a05a208db9a2c5dcbe123c1/numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19", size = 6738039, upload-time = "2025-09-09T15:57:34.328Z" }, + { url = "https://files.pythonhosted.org/packages/55/52/af46ac0795e09657d45a7f4db961917314377edecf66db0e39fa7ab5c3d3/numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30", size = 14352314, upload-time = "2025-09-09T15:57:36.255Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b1/dc226b4c90eb9f07a3fff95c2f0db3268e2e54e5cce97c4ac91518aee71b/numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e", size = 16701722, upload-time = "2025-09-09T15:57:38.622Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9d/9d8d358f2eb5eced14dba99f110d83b5cd9a4460895230f3b396ad19a323/numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3", size = 16132755, upload-time = "2025-09-09T15:57:41.16Z" }, + { url = "https://files.pythonhosted.org/packages/b6/27/b3922660c45513f9377b3fb42240bec63f203c71416093476ec9aa0719dc/numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea", size = 18651560, upload-time = "2025-09-09T15:57:43.459Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8e/3ab61a730bdbbc201bb245a71102aa609f0008b9ed15255500a99cd7f780/numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd", size = 6442776, upload-time = "2025-09-09T15:57:45.793Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3a/e22b766b11f6030dc2decdeff5c2fb1610768055603f9f3be88b6d192fb2/numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d", size = 12927281, upload-time = "2025-09-09T15:57:47.492Z" }, + { url = "https://files.pythonhosted.org/packages/7b/42/c2e2bc48c5e9b2a83423f99733950fbefd86f165b468a3d85d52b30bf782/numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1", size = 10265275, upload-time = "2025-09-09T15:57:49.647Z" }, + { url = "https://files.pythonhosted.org/packages/6b/01/342ad585ad82419b99bcf7cebe99e61da6bedb89e213c5fd71acc467faee/numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593", size = 20951527, upload-time = "2025-09-09T15:57:52.006Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d8/204e0d73fc1b7a9ee80ab1fe1983dd33a4d64a4e30a05364b0208e9a241a/numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652", size = 14186159, upload-time = "2025-09-09T15:57:54.407Z" }, + { url = "https://files.pythonhosted.org/packages/22/af/f11c916d08f3a18fb8ba81ab72b5b74a6e42ead4c2846d270eb19845bf74/numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7", size = 5114624, upload-time = "2025-09-09T15:57:56.5Z" }, + { url = "https://files.pythonhosted.org/packages/fb/11/0ed919c8381ac9d2ffacd63fd1f0c34d27e99cab650f0eb6f110e6ae4858/numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a", size = 6642627, upload-time = "2025-09-09T15:57:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/ee/83/deb5f77cb0f7ba6cb52b91ed388b47f8f3c2e9930d4665c600408d9b90b9/numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe", size = 14296926, upload-time = "2025-09-09T15:58:00.035Z" }, + { url = "https://files.pythonhosted.org/packages/77/cc/70e59dcb84f2b005d4f306310ff0a892518cc0c8000a33d0e6faf7ca8d80/numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421", size = 16638958, upload-time = "2025-09-09T15:58:02.738Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5a/b2ab6c18b4257e099587d5b7f903317bd7115333ad8d4ec4874278eafa61/numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021", size = 16071920, upload-time = "2025-09-09T15:58:05.029Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f1/8b3fdc44324a259298520dd82147ff648979bed085feeacc1250ef1656c0/numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf", size = 18577076, upload-time = "2025-09-09T15:58:07.745Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a1/b87a284fb15a42e9274e7fcea0dad259d12ddbf07c1595b26883151ca3b4/numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0", size = 6366952, upload-time = "2025-09-09T15:58:10.096Z" }, + { url = "https://files.pythonhosted.org/packages/70/5f/1816f4d08f3b8f66576d8433a66f8fa35a5acfb3bbd0bf6c31183b003f3d/numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8", size = 12919322, upload-time = "2025-09-09T15:58:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/8c/de/072420342e46a8ea41c324a555fa90fcc11637583fb8df722936aed1736d/numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe", size = 10478630, upload-time = "2025-09-09T15:58:14.64Z" }, + { url = "https://files.pythonhosted.org/packages/d5/df/ee2f1c0a9de7347f14da5dd3cd3c3b034d1b8607ccb6883d7dd5c035d631/numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00", size = 21047987, upload-time = "2025-09-09T15:58:16.889Z" }, + { url = "https://files.pythonhosted.org/packages/d6/92/9453bdc5a4e9e69cf4358463f25e8260e2ffc126d52e10038b9077815989/numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a", size = 14301076, upload-time = "2025-09-09T15:58:20.343Z" }, + { url = "https://files.pythonhosted.org/packages/13/77/1447b9eb500f028bb44253105bd67534af60499588a5149a94f18f2ca917/numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d", size = 5229491, upload-time = "2025-09-09T15:58:22.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f9/d72221b6ca205f9736cb4b2ce3b002f6e45cd67cd6a6d1c8af11a2f0b649/numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a", size = 6737913, upload-time = "2025-09-09T15:58:24.569Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/d12834711962ad9c46af72f79bb31e73e416ee49d17f4c797f72c96b6ca5/numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54", size = 14352811, upload-time = "2025-09-09T15:58:26.416Z" }, + { url = "https://files.pythonhosted.org/packages/a1/0d/fdbec6629d97fd1bebed56cd742884e4eead593611bbe1abc3eb40d304b2/numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e", size = 16702689, upload-time = "2025-09-09T15:58:28.831Z" }, + { url = "https://files.pythonhosted.org/packages/9b/09/0a35196dc5575adde1eb97ddfbc3e1687a814f905377621d18ca9bc2b7dd/numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097", size = 16133855, upload-time = "2025-09-09T15:58:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ca/c9de3ea397d576f1b6753eaa906d4cdef1bf97589a6d9825a349b4729cc2/numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970", size = 18652520, upload-time = "2025-09-09T15:58:33.762Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c2/e5ed830e08cd0196351db55db82f65bc0ab05da6ef2b72a836dcf1936d2f/numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5", size = 6515371, upload-time = "2025-09-09T15:58:36.04Z" }, + { url = "https://files.pythonhosted.org/packages/47/c7/b0f6b5b67f6788a0725f744496badbb604d226bf233ba716683ebb47b570/numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f", size = 13112576, upload-time = "2025-09-09T15:58:37.927Z" }, + { url = "https://files.pythonhosted.org/packages/06/b9/33bba5ff6fb679aa0b1f8a07e853f002a6b04b9394db3069a1270a7784ca/numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b", size = 10545953, upload-time = "2025-09-09T15:58:40.576Z" }, ] [[package]] name = "p2api" -version = "1.0.11" +version = "1.0.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "keyring" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/54/2181f0b2c85c3515d01b8a99fe70bd8e1dc0c673a4f6f1dbd512f26ce441/p2api-1.0.11.tar.gz", hash = "sha256:779fcb6f5502a210eba39e64026348dcbcfe256208afde5d199645d9d2b943d6", size = 24994, upload-time = "2026-02-19T09:41:41.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/94/96ece6cf22552578a16f82ccedabc1d067f65ba871cfec60324a2eb51d12/p2api-1.0.10.tar.gz", hash = "sha256:9d9d41d978fb5143c9eb1e7119bd8bd7ff1c0f74b267d2fef425e59c717b35ef", size = 25017, upload-time = "2024-09-12T12:56:51.453Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/ce/7158780102f6878279ee358d36f8ad13e45061a0a8c3bf552d37cfe61c22/p2api-1.0.11-py2.py3-none-any.whl", hash = "sha256:8eb1b51c0cee59b3292434dffc36c4b0b34c787b1728aeba73c036d017d8901c", size = 26415, upload-time = "2026-02-19T09:41:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/07ff4a0be52675076fea31558ed4c4e880887df606bcdd395ac8b2802298/p2api-1.0.10-py2.py3-none-any.whl", hash = "sha256:0d1d1edfbbc9202174147fa34117522db6c27af4b4d3e86adad7458f2df6965b", size = 26400, upload-time = "2024-09-12T12:56:49.809Z" }, ] [[package]] name = "packaging" -version = "26.2" +version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] @@ -711,16 +646,16 @@ wheels = [ [[package]] name = "pycparser" -version = "3.0" +version = "2.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, ] [[package]] name = "pydantic" -version = "2.13.3" +version = "2.11.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -728,98 +663,65 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981, upload-time = "2026-04-20T14:46:41.402Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" }, ] [[package]] name = "pydantic-core" -version = "2.46.3" +version = "2.33.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/cb/5b47425556ecc1f3fe18ed2a0083188aa46e1dd812b06e406475b3a5d536/pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67", size = 2101946, upload-time = "2026-04-20T14:40:52.581Z" }, - { url = "https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089", size = 1951612, upload-time = "2026-04-20T14:42:42.996Z" }, - { url = "https://files.pythonhosted.org/packages/50/6e/b7348fd30d6556d132cddd5bd79f37f96f2601fe0608afac4f5fb01ec0b3/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0", size = 1977027, upload-time = "2026-04-20T14:42:02.001Z" }, - { url = "https://files.pythonhosted.org/packages/82/11/31d60ee2b45540d3fb0b29302a393dbc01cd771c473f5b5147bcd353e593/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789", size = 2063008, upload-time = "2026-04-20T14:44:17.952Z" }, - { url = "https://files.pythonhosted.org/packages/8a/db/3a9d1957181b59258f44a2300ab0f0be9d1e12d662a4f57bb31250455c52/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d", size = 2233082, upload-time = "2026-04-20T14:40:57.934Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e1/3277c38792aeb5cfb18c2f0c5785a221d9ff4e149abbe1184d53d5f72273/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c", size = 2304615, upload-time = "2026-04-20T14:42:12.584Z" }, - { url = "https://files.pythonhosted.org/packages/5e/d5/e3d9717c9eba10855325650afd2a9cba8e607321697f18953af9d562da2f/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395", size = 2094380, upload-time = "2026-04-20T14:43:05.522Z" }, - { url = "https://files.pythonhosted.org/packages/a1/20/abac35dedcbfd66c6f0b03e4e3564511771d6c9b7ede10a362d03e110d9b/pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396", size = 2135429, upload-time = "2026-04-20T14:41:55.549Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a5/41bfd1df69afad71b5cf0535055bccc73022715ad362edbc124bc1e021d7/pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d", size = 2174582, upload-time = "2026-04-20T14:41:45.96Z" }, - { url = "https://files.pythonhosted.org/packages/79/65/38d86ea056b29b2b10734eb23329b7a7672ca604df4f2b6e9c02d4ee22fe/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca", size = 2187533, upload-time = "2026-04-20T14:40:55.367Z" }, - { url = "https://files.pythonhosted.org/packages/b6/55/a1129141678a2026badc539ad1dee0a71d06f54c2f06a4bd68c030ac781b/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976", size = 2332985, upload-time = "2026-04-20T14:44:13.05Z" }, - { url = "https://files.pythonhosted.org/packages/d7/60/cb26f4077719f709e54819f4e8e1d43f4091f94e285eb6bd21e1190a7b7c/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b", size = 2373670, upload-time = "2026-04-20T14:41:53.421Z" }, - { url = "https://files.pythonhosted.org/packages/6b/7e/c3f21882bdf1d8d086876f81b5e296206c69c6082551d776895de7801fa0/pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4", size = 1966722, upload-time = "2026-04-20T14:44:30.588Z" }, - { url = "https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1", size = 2072970, upload-time = "2026-04-20T14:42:54.248Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f8/a989b21cc75e9a32d24192ef700eea606521221a89faa40c919ce884f2b1/pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72", size = 2035963, upload-time = "2026-04-20T14:44:20.4Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109, upload-time = "2026-04-20T14:41:24.219Z" }, - { url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820, upload-time = "2026-04-20T14:42:14.236Z" }, - { url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785, upload-time = "2026-04-20T14:42:31.648Z" }, - { url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761, upload-time = "2026-04-20T14:40:37.093Z" }, - { url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989, upload-time = "2026-04-20T14:42:59.254Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975, upload-time = "2026-04-20T14:41:32.287Z" }, - { url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325, upload-time = "2026-04-20T14:42:47.685Z" }, - { url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368, upload-time = "2026-04-20T14:41:22.766Z" }, - { url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908, upload-time = "2026-04-20T14:42:52.044Z" }, - { url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422, upload-time = "2026-04-20T14:43:29.55Z" }, - { url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709, upload-time = "2026-04-20T14:42:16.134Z" }, - { url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428, upload-time = "2026-04-20T14:41:10.974Z" }, - { url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601, upload-time = "2026-04-20T14:41:42.128Z" }, - { url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517, upload-time = "2026-04-20T14:43:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802, upload-time = "2026-04-20T14:43:38.507Z" }, - { url = "https://files.pythonhosted.org/packages/7f/db/a7bcb4940183fda36022cd18ba8dd12f2dff40740ec7b58ce7457befa416/pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b", size = 2097614, upload-time = "2026-04-20T14:44:38.374Z" }, - { url = "https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018", size = 1951896, upload-time = "2026-04-20T14:40:53.996Z" }, - { url = "https://files.pythonhosted.org/packages/87/92/37cf4049d1636996e4b888c05a501f40a43ff218983a551d57f9d5e14f0d/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34", size = 1979314, upload-time = "2026-04-20T14:41:49.446Z" }, - { url = "https://files.pythonhosted.org/packages/d8/36/9ff4d676dfbdfb2d591cf43f3d90ded01e15b1404fd101180ed2d62a2fd3/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7", size = 2056133, upload-time = "2026-04-20T14:42:23.574Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f0/405b442a4d7ba855b06eec8b2bf9c617d43b8432d099dfdc7bf999293495/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2", size = 2228726, upload-time = "2026-04-20T14:44:22.816Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f8/65cd92dd5a0bd89ba277a98ecbfaf6fc36bbd3300973c7a4b826d6ab1391/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0087084960f209a9a4af50ecd1fb063d9ad3658c07bb81a7a53f452dacbfb2ba", size = 2301214, upload-time = "2026-04-20T14:44:48.792Z" }, - { url = "https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f", size = 2099927, upload-time = "2026-04-20T14:41:40.196Z" }, - { url = "https://files.pythonhosted.org/packages/6d/53/269caf30e0096e0a8a8f929d1982a27b3879872cca2d917d17c2f9fdf4fe/pydantic_core-2.46.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f1771ce258afb3e4201e67d154edbbae712a76a6081079fe247c2f53c6322c22", size = 2128789, upload-time = "2026-04-20T14:41:15.868Z" }, - { url = "https://files.pythonhosted.org/packages/00/b0/1a6d9b6a587e118482910c244a1c5acf4d192604174132efd12bf0ac486f/pydantic_core-2.46.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7610b6a5242a6c736d8ad47fd5fff87fcfe8f833b281b1c409c3d6835d9227f", size = 2173815, upload-time = "2026-04-20T14:44:25.152Z" }, - { url = "https://files.pythonhosted.org/packages/87/56/e7e00d4041a7e62b5a40815590114db3b535bf3ca0bf4dca9f16cef25246/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127", size = 2181608, upload-time = "2026-04-20T14:41:28.933Z" }, - { url = "https://files.pythonhosted.org/packages/e8/22/4bd23c3d41f7c185d60808a1de83c76cf5aeabf792f6c636a55c3b1ec7f9/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c", size = 2326968, upload-time = "2026-04-20T14:42:03.962Z" }, - { url = "https://files.pythonhosted.org/packages/24/ac/66cd45129e3915e5ade3b292cb3bc7fd537f58f8f8dbdaba6170f7cabb74/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1", size = 2369842, upload-time = "2026-04-20T14:41:35.52Z" }, - { url = "https://files.pythonhosted.org/packages/a2/51/dd4248abb84113615473aa20d5545b7c4cd73c8644003b5259686f93996c/pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505", size = 1959661, upload-time = "2026-04-20T14:41:00.042Z" }, - { url = "https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e", size = 2071686, upload-time = "2026-04-20T14:43:16.471Z" }, - { url = "https://files.pythonhosted.org/packages/8c/db/1cf77e5247047dfee34bc01fa9bca134854f528c8eb053e144298893d370/pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df", size = 2026907, upload-time = "2026-04-20T14:43:31.732Z" }, - { url = "https://files.pythonhosted.org/packages/57/c0/b3df9f6a543276eadba0a48487b082ca1f201745329d97dbfa287034a230/pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf", size = 2095047, upload-time = "2026-04-20T14:42:37.982Z" }, - { url = "https://files.pythonhosted.org/packages/66/57/886a938073b97556c168fd99e1a7305bb363cd30a6d2c76086bf0587b32a/pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee", size = 1934329, upload-time = "2026-04-20T14:43:49.655Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7c/b42eaa5c34b13b07ecb51da21761297a9b8eb43044c864a035999998f328/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a", size = 1974847, upload-time = "2026-04-20T14:42:10.737Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9b/92b42db6543e7de4f99ae977101a2967b63122d4b6cf7773812da2d7d5b5/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c", size = 2041742, upload-time = "2026-04-20T14:40:44.262Z" }, - { url = "https://files.pythonhosted.org/packages/0f/19/46fbe1efabb5aa2834b43b9454e70f9a83ad9c338c1291e48bdc4fecf167/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1", size = 2236235, upload-time = "2026-04-20T14:41:27.307Z" }, - { url = "https://files.pythonhosted.org/packages/77/da/b3f95bc009ad60ec53120f5d16c6faa8cabdbe8a20d83849a1f2b8728148/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13936129ce841f2a5ddf6f126fea3c43cd128807b5a59588c37cf10178c2e64", size = 2282633, upload-time = "2026-04-20T14:44:33.271Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6e/401336117722e28f32fb8220df676769d28ebdf08f2f4469646d404c43a3/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28b5f2ef03416facccb1c6ef744c69793175fd27e44ef15669201601cf423acb", size = 2109679, upload-time = "2026-04-20T14:44:41.065Z" }, - { url = "https://files.pythonhosted.org/packages/fc/53/b289f9bc8756a32fe718c46f55afaeaf8d489ee18d1a1e7be1db73f42cc4/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:830d1247d77ad23852314f069e9d7ddafeec5f684baf9d7e7065ed46a049c4e6", size = 2108342, upload-time = "2026-04-20T14:42:50.144Z" }, - { url = "https://files.pythonhosted.org/packages/10/5b/8292fc7c1f9111f1b2b7c1b0dcf1179edcd014fc3ea4517499f50b829d71/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0793c90c1a3c74966e7975eaef3ed30ebdff3260a0f815a62a22adc17e4c01c", size = 2157208, upload-time = "2026-04-20T14:42:08.133Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9e/f80044e9ec07580f057a89fc131f78dda7a58751ddf52bbe05eaf31db50f/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47", size = 2167237, upload-time = "2026-04-20T14:42:25.412Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/6781a1b037f3b96be9227edbd1101f6d3946746056231bf4ac48cdff1a8d/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab", size = 2312540, upload-time = "2026-04-20T14:40:40.313Z" }, - { url = "https://files.pythonhosted.org/packages/3e/db/19c0839feeb728e7df03255581f198dfdf1c2aeb1e174a8420b63c5252e5/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba", size = 2369556, upload-time = "2026-04-20T14:41:09.427Z" }, - { url = "https://files.pythonhosted.org/packages/e0/15/3228774cb7cd45f5f721ddf1b2242747f4eb834d0c491f0c02d606f09fed/pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56", size = 1949756, upload-time = "2026-04-20T14:41:25.717Z" }, - { url = "https://files.pythonhosted.org/packages/b8/2a/c79cf53fd91e5a87e30d481809f52f9a60dd221e39de66455cf04deaad37/pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8", size = 2051305, upload-time = "2026-04-20T14:43:18.627Z" }, - { url = "https://files.pythonhosted.org/packages/0b/db/d8182a7f1d9343a032265aae186eb063fe26ca4c40f256b21e8da4498e89/pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374", size = 2026310, upload-time = "2026-04-20T14:41:01.778Z" }, - { url = "https://files.pythonhosted.org/packages/34/42/f426db557e8ab2791bc7562052299944a118655496fbff99914e564c0a94/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b12dd51f1187c2eb489af8e20f880362db98e954b54ab792fa5d92e8bcc6b803", size = 2091877, upload-time = "2026-04-20T14:43:27.091Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4f/86a832a9d14df58e663bfdf4627dc00d3317c2bd583c4fb23390b0f04b8e/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f00a0961b125f1a47af7bcc17f00782e12f4cd056f83416006b30111d941dfa3", size = 1932428, upload-time = "2026-04-20T14:40:45.781Z" }, - { url = "https://files.pythonhosted.org/packages/11/1a/fe857968954d93fb78e0d4b6df5c988c74c4aaa67181c60be7cfe327c0ca/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57697d7c056aca4bbb680200f96563e841a6386ac1129370a0102592f4dddff5", size = 1997550, upload-time = "2026-04-20T14:44:02.425Z" }, - { url = "https://files.pythonhosted.org/packages/17/eb/9d89ad2d9b0ba8cd65393d434471621b98912abb10fbe1df08e480ba57b5/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd35aa21299def8db7ef4fe5c4ff862941a9a158ca7b63d61e66fe67d30416b4", size = 2137657, upload-time = "2026-04-20T14:42:45.149Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, ] [[package]] name = "pydantic-settings" -version = "2.14.0" +version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/98/c8345dccdc31de4228c039a98f6467a941e39558da41c1744fbe29fa5666/pydantic_settings-2.14.0.tar.gz", hash = "sha256:24285fd4b0e0c06507dd9fdfd331ee23794305352aaec8fc4eb92d4047aeb67d", size = 235709, upload-time = "2026-04-20T13:37:40.293Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" }, + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, ] [[package]] @@ -842,16 +744,16 @@ wheels = [ [[package]] name = "pygments" -version = "2.20.0" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] name = "pytest" -version = "9.0.3" +version = "8.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -860,18 +762,18 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] [[package]] name = "python-dotenv" -version = "1.2.2" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] [[package]] @@ -885,53 +787,33 @@ wheels = [ [[package]] name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] [[package]] name = "requests" -version = "2.33.1" +version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -939,22 +821,31 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] name = "secretstorage" -version = "3.5.0" +version = "3.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "jeepney" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/9f/11ef35cf1027c1339552ea7bfe6aaa74a8516d8b5caf6e7d338daf54fd80/secretstorage-3.4.0.tar.gz", hash = "sha256:c46e216d6815aff8a8a18706a2fbfd8d53fcbb0dce99301881687a1b0289ef7c", size = 19748, upload-time = "2025-09-09T16:42:13.859Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/ff/2e2eed29e02c14a5cb6c57f09b2d5b40e65d6cc71f45b52e0be295ccbc2f/secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e", size = 15272, upload-time = "2025-09-09T16:42:12.744Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] [[package]] @@ -977,11 +868,11 @@ wheels = [ [[package]] name = "textcase" -version = "0.4.5" +version = "0.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c0/08/991940591c7a8de25505a4e43642d1c2b54f43c3ed76f8c90f1df6ee9525/textcase-0.4.5.tar.gz", hash = "sha256:97fd08754b7dba9bfa5daf4ace474645f6b66409fb00f483c5de729e730f8e52", size = 6988, upload-time = "2025-10-10T15:11:25.476Z" } +sdist = { url = "https://files.pythonhosted.org/packages/75/e6/5499333512a7008cfe795c4e2db404c34c61da3446727b31bfd2fcf79783/textcase-0.4.3.tar.gz", hash = "sha256:1900dda7d5fc8cf44c414677b9be8b66b2354d9ff4107934ed577f11c1d1210a", size = 6946, upload-time = "2025-04-18T16:13:21.712Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/48/6ea42c749608cfca883d5d8fae03edbcde636090d2bc751fd5da9157b451/textcase-0.4.5-py3-none-any.whl", hash = "sha256:bd5d6aaf653b339e3ac60ad96cfc960a94219a97da464eddeed7084f41774937", size = 6503, upload-time = "2025-10-10T15:11:24.294Z" }, + { url = "https://files.pythonhosted.org/packages/a0/a3/7e8d624362ca16a51df435d14c08e07adf266c27d16fad422f954a3abbbd/textcase-0.4.3-py3-none-any.whl", hash = "sha256:2f1a1c069ba60e224455ae6d45ca35344ad6a2ffffd05d6bd8161c0c34515f30", size = 6474, upload-time = "2025-04-18T16:13:20.621Z" }, ] [[package]] @@ -1067,21 +958,21 @@ wheels = [ [[package]] name = "typing-inspection" -version = "0.4.2" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] [[package]] name = "urllib3" -version = "2.6.3" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] From 25ceffc668a185d3ca7eb9ae501515e0adfa5bad Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 7 May 2026 12:28:22 +0200 Subject: [PATCH 166/171] Update PyAstroSALT dependency --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 155a5e8..a5db2c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ salt = [ "beautifulsoup4>=4.14.3", "jinja2>=3.1.6", "lxml>=6.0.1", - "pyastrosalt>=0.2.1", + "pyastrosalt>=0.2.2", ] [tool.pytest.ini_options] diff --git a/uv.lock b/uv.lock index 11e09b2..3b20e2a 100644 --- a/uv.lock +++ b/uv.lock @@ -50,7 +50,7 @@ requires-dist = [ { name = "lxml", marker = "extra == 'salt'", specifier = ">=6.0.1" }, { name = "lxml-stubs", marker = "extra == 'lt'", specifier = ">=0.5.1" }, { name = "p2api", marker = "extra == 'eso'", specifier = ">=1.0.10" }, - { name = "pyastrosalt", marker = "extra == 'salt'", specifier = ">=0.2.1" }, + { name = "pyastrosalt", marker = "extra == 'salt'", specifier = ">=0.2.2" }, { name = "pydantic", specifier = ">=2.11.1" }, { name = "pydantic-settings", specifier = ">=2.9.1" }, { name = "suds", marker = "extra == 'lt'", specifier = ">=1.2.0" }, @@ -632,16 +632,16 @@ wheels = [ [[package]] name = "pyastrosalt" -version = "0.2.1" +version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "defusedxml" }, { name = "requests" }, { name = "types-defusedxml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/79/fee35e0b4ae20729caf841605973a0bbc82703ec7494aa3a306729e5844e/pyastrosalt-0.2.1.tar.gz", hash = "sha256:fca18a4917432b76027556e9796e51144a11eaeca2d1a974e0625e84ed8d3699", size = 16185, upload-time = "2026-04-28T15:24:27.231Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/1c/27756fc0b4e058fc9f9d6b5246661a285c671e9e8008236a6fecb3e17220/pyastrosalt-0.2.2.tar.gz", hash = "sha256:ae6debbc552f3592260fde1504e59d46ca56dd82400cd59e3a03f9a4b324e637", size = 18419, upload-time = "2026-05-07T09:48:10.25Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/05/4c17fa38c7dd8c229915551b25857b790b5067207a7e5588f28a8a8e0759/pyastrosalt-0.2.1-py3-none-any.whl", hash = "sha256:372c930e02f430b01afcae6e3044a2b4142e28257a57c1e8a3639bdfcfcd3cce", size = 12156, upload-time = "2026-04-28T15:24:26.16Z" }, + { url = "https://files.pythonhosted.org/packages/68/9f/469888b597ad0017f78228efef10017c9cd7c1f5077c2fa20c9805734dd0/pyastrosalt-0.2.2-py3-none-any.whl", hash = "sha256:7ed089864166b3fd1fe5c79936e2f8f3b547aa696c899bf86d701368a82590e7", size = 14198, upload-time = "2026-05-07T09:48:11.183Z" }, ] [[package]] From 3b3859b318442b61d21db9e17bb67e7c4c41ee97 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 7 May 2026 12:29:24 +0200 Subject: [PATCH 167/171] Add the SALT facility class --- src/aeonlib/salt/facility.py | 78 ++++++++++++++++++++++++++++++++++++ tests/salt/test_online.py | 33 +++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/aeonlib/salt/facility.py create mode 100644 tests/salt/test_online.py diff --git a/src/aeonlib/salt/facility.py b/src/aeonlib/salt/facility.py new file mode 100644 index 0000000..e4e5c48 --- /dev/null +++ b/src/aeonlib/salt/facility.py @@ -0,0 +1,78 @@ +import io +import logging + +from pyastrosalt.session import Session +from pyastrosalt.submission import Submission, validate, submit + +from aeonlib.conf import settings +from aeonlib.salt.models import Request + +logger = logging.getLogger(__name__) + + +class SALTFacility: + """ + SALT facility for validating and submitting SALT observation requests. + + By default, the facility is using the production server. For testing purposes you + should call the facility constructor with the `use_playground` parameter set to + `True`. + """ + + def __init__(self, use_playground: bool = False): + self._session = Session() + if use_playground: + self._session.use_playground() + username = settings.salt_username + if not username: + raise ValueError("salt_username is not set.") + password = settings.salt_password + if not password: + raise ValueError("salt_password is not set.") + self._session.login(username, password) + + def validate(self, request: Request) -> tuple[bool, list[str]]: + """ + Send an observation request to the server for validation. + + The method waits for the validation request to complete and then returns a tuple + with a boolean indicating whether the request is valid (`True`) or invalid + (`False`) and the list of errors found by the validation. + + Parameters + ---------- + request + The observation request. + + Returns + ------- + A tuple of a boolean indicating whether the request is valid and the list of + errors. + """ + logger.debug(f"Validating request:\n{request.model_dump()}") + zip_content = io.BytesIO() + request.export(zip_content) + zip_content.seek(0) + return validate(self._session, zip_content, request.proposal_code) + + def submit(self, request: Request) -> Submission: + """ + Submit an observation request. + + The method returns a `pyastrosalt.submission.Submision` object, which you can + use to follow the submission progress. + + Parameters + ---------- + request + The observation request. + + Returns + ------- + A `pyastrosalt.submission.Submission` object. + """ + logger.debug(f"Submitting request:\n{request.model_dump()}") + zip_content = io.BytesIO() + request.export(zip_content) + zip_content.seek(0) + return submit(self._session, zip_content, request.proposal_code) diff --git a/tests/salt/test_online.py b/tests/salt/test_online.py new file mode 100644 index 0000000..01fa4e5 --- /dev/null +++ b/tests/salt/test_online.py @@ -0,0 +1,33 @@ +from datetime import datetime, timedelta + +import pytest +from aeonlib.models import Window +from aeonlib.salt.facility import SALTFacility + +window = Window( + start=datetime.now(), + end=datetime.now() + timedelta(days=365), +) + +# Replace with an appropriate proposal code. +proposal_code = "2026-1-DDT-002" + + +@pytest.mark.online +def test_validate(base_request): + # Test online proposal validation.""" + base_request.proposal_code = proposal_code + base_request.blocks[0].windows = [window] + facility = SALTFacility(use_playground=True) + valid, errors = facility.validate(base_request) + assert valid or errors + + +@pytest.mark.online +def test_submit(base_request): + # Test online proposal validation.""" + base_request.proposal_code = proposal_code + base_request.blocks[0].windows = [window] + facility = SALTFacility(use_playground=True) + submission = facility.submit(base_request) + assert submission From 5e4c69e98741aec6811838d3f3d94926abb06a65 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 7 May 2026 12:29:55 +0200 Subject: [PATCH 168/171] Add an example notebook for SALT requests --- examples/SALT.ipynb | 323 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 examples/SALT.ipynb diff --git a/examples/SALT.ipynb b/examples/SALT.ipynb new file mode 100644 index 0000000..ab19dc2 --- /dev/null +++ b/examples/SALT.ipynb @@ -0,0 +1,323 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "27270ca2181b357f", + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } + }, + "source": [ + " # Submitting a SALT observation request\n", + "\n", + " This example illustrates how to submit an observation request to the Southern African Large Telescope (SALT). It assumes that you already had a look at the notebook for LCO anmd ESO requests." + ] + }, + { + "cell_type": "markdown", + "id": "213d6d0667c7ed71", + "metadata": {}, + "source": [ + "## Configuration\n", + "\n", + "You need four pieces of configuration details:\n", + "\n", + "1. Your SALT username. This is defined as the `salt_username` property of `aeonlib.conf.Settings`.\n", + "2. Your SALT password. This is defined as the `salt_password` property of `aeonlib.conf.Settings`.\n", + "3. The proposal code. This must be the proposal code of an existing proposal, and you must be allowed to resubmit this proposal.\n", + "4. The semester in the format yyyy-s, with the year yyyy and the semester (1 or 2) s." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c07b4b623831022f", + "metadata": { + "ExecuteTime": { + "end_time": "2026-05-07T09:38:47.929188Z", + "start_time": "2026-05-07T09:38:46.710824Z" + } + }, + "outputs": [], + "source": [ + "from datetime import datetime, timedelta\n", + "from pprint import pprint\n", + "from time import sleep\n", + "\n", + "from astropy import units as u\n", + "from pyastrosalt.submission import SubmissionStatus\n", + "\n", + "from aeonlib.models import Window\n", + "from aeonlib.salt.facility import SALTFacility\n", + "from aeonlib.salt.models import SaltSiderealTarget, MagnitudeRange, Hrs, HrsDetector, Constraints, Acquisition, Block, \\\n", + " Request\n", + "\n", + "# Replace with the correct proposal code and semester\n", + "proposal_code = \"2025-2-DDT-005\"\n", + "semester = \"2026-1\"" + ] + }, + { + "cell_type": "markdown", + "id": "3865bdbe5db287be", + "metadata": {}, + "source": [ + "## Create the observation request\n", + "\n", + "The observation request is defined using models from `aeonlib.salt.models`. This example is for illustrative purposes only." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cf9cb87420a39f4c", + "metadata": { + "ExecuteTime": { + "end_time": "2026-05-07T09:38:48.573672Z", + "start_time": "2026-05-07T09:38:48.509144Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'blocks': [{'acquisition': {'do_not_flip_position_angle': False,\n", + " 'exposure_time': np.float64(1.0),\n", + " 'filter': 'Johnson V',\n", + " 'finder_charts': [],\n", + " 'include_focused_image': False,\n", + " 'position_angle': np.float64(50.0),\n", + " 'reference_star': None},\n", + " 'comments': None,\n", + " 'constraints': {'max_lunar_phase_percentage': 50.0,\n", + " 'max_seeing': 2.0,\n", + " 'min_lunar_distance': np.float64(30.0),\n", + " 'transparency': 'Clear'},\n", + " 'data_notification': 'Normal',\n", + " 'identifier': '45ca2cd5-9942-4120-a2d9-ff686e9b0a30',\n", + " 'instrument': {'blue_arm': {'exposure_times': [np.float64(10.0)]},\n", + " 'fibre_separation': np.float64(0.016666666666666666),\n", + " 'instrument_name': 'HRS',\n", + " 'mode': 'LOW RESOLUTION',\n", + " 'num_cycles': 1,\n", + " 'prv_calibration': None,\n", + " 'red_arm': {'exposure_times': [np.float64(10.0)]}},\n", + " 'max_num_visits': None,\n", + " 'min_nights_between_visits': 0,\n", + " 'name': 'Sombrero Galaxy',\n", + " 'num_visits': 1,\n", + " 'pool': None,\n", + " 'priority': 1,\n", + " 'ranking': 'High',\n", + " 'target': {'altitude': None,\n", + " 'azimuth': None,\n", + " 'dec': np.float64(-11.623088333333333),\n", + " 'epoch': 2000,\n", + " 'hour_angle': None,\n", + " 'magnitude_range': {'bandpass': 'V',\n", + " 'max_magnitude': 8.0,\n", + " 'min_magnitude': 8.0},\n", + " 'name': 'Sombrero Galaxy',\n", + " 'parallax': 0,\n", + " 'proper_motion_dec': 0,\n", + " 'proper_motion_ra': 0,\n", + " 'ra': np.float64(189.99763083333332),\n", + " 'target_type': 'Galaxy',\n", + " 'type': 'ICRS'},\n", + " 'windows': [{'end': datetime.datetime(2026, 6, 6, 12, 0, 7, 431184),\n", + " 'start': datetime.datetime(2026, 5, 8, 12, 0, 7, 431175)}]}],\n", + " 'proposal_code': '2025-2-DDT-005',\n", + " 'semester': '2025-2'}\n" + ] + } + ], + "source": [ + "# Observe the Sombrero Galaxy\n", + "sombrero = SaltSiderealTarget(\n", + " name=\"Sombrero Galaxy\",\n", + " type=\"ICRS\",\n", + " ra=\"12h 39m 59.4314s\",\n", + " dec=\"−11° 37′ 23.118\",\n", + " target_type=\"Galaxy\",\n", + " magnitude_range=MagnitudeRange(min_magnitude=8.0, max_magnitude=8.0, bandpass=\"V\")\n", + ")\n", + "\n", + "# Use the High Resolution Spectrograph\n", + "blue_arm = HrsDetector(exposure_times=[10 * u.s])\n", + "red_arm = HrsDetector(exposure_times=[10 * u.s])\n", + "hrs = Hrs(mode=\"low resolution\", blue_arm=blue_arm, red_arm=red_arm)\n", + "\n", + "# Define observing constraints\n", + "constraints = Constraints(\n", + " transparency=\"clear\",\n", + " max_lunar_phase_percentage=50,\n", + " min_lunar_distance=30 * u.deg,\n", + " max_seeing=2,\n", + ")\n", + "\n", + "# Define the acquisition. Finder charts are optional\n", + "acquisition = Acquisition(\n", + " finder_charts=[],\n", + " position_angle=50 * u.deg,\n", + ")\n", + "\n", + "# Window bounds can be specified as datetimes or Astropy.time.Time objects\n", + "window = Window(start=datetime.now() + timedelta(days=1), end=datetime.now() + timedelta(days=30))\n", + "\n", + "# Define the observation block\n", + "block = Block(\n", + " name=f\"Sombrero Galaxy\",\n", + " priority=1,\n", + " ranking=\"high\",\n", + " num_visits=1,\n", + " constraints=constraints,\n", + " windows=[window],\n", + " target=sombrero,\n", + " acquisition=acquisition,\n", + " instrument=hrs,\n", + ")\n", + "\n", + "request = Request(proposal_code=proposal_code, semester=\"2025-2\", blocks=[block])\n", + "\n", + "pprint(request.model_dump())\n" + ] + }, + { + "cell_type": "markdown", + "id": "298840c7a9cffb06", + "metadata": {}, + "source": [ + "## Validate the observation request\n", + "\n", + "The observation request can be validated with the `validate` method of the SALT facility.\n", + "\n", + "Note that we call the facility constructor with its `use_playground` parameter set to `True`. This ensures that a test server rather than the production server is used. The default is to use the production server.\n", + "\n", + "As your observation request is sent to the server, it may take a few moments before the validation completes." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "617c2ad50e26bf7c", + "metadata": { + "ExecuteTime": { + "end_time": "2026-05-07T09:39:29.750203Z", + "start_time": "2026-05-07T09:39:29.688225Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "environ({'COMMAND_MODE': 'unix2003', 'TERM_SESSION_ID': 'ec268349-b3f9-4a8d-b0e0-bc976fb4745d', 'SHELL': '/bin/zsh', 'TMPDIR': '/var/folders/3l/v9_k6g5s1174c0nz5dz00jv80000gn/T/', '__CFBundleIdentifier': 'com.jetbrains.pycharm', 'ENABLE_IDE_INTEGRATION': 'true', 'JEDITERM_SOURCE_ARGS': '', 'HOME': '/Users/christian', 'PATH': '/Users/christian/IdeaProjects/AEONlib/.venv/bin:/Users/christian/.bun/bin:/Users/christian/.pyenv/shims:/Users/christian/Documents/Scripts:/Library/PostgreSQL/13/bin:/usr/local/Cellar/bash/5.2.21/bin:/usr/local/opt/mysql-client@5.7/bin:/usr/local/opt/llvm/bin:/usr/local/Cellar/postgresql@18/18.3/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/Library/Frameworks/Python.framework/Versions/3.11/bin:/Library/Frameworks/Python.framework/Versions/2.7/bin:/Library/Frameworks/Python.framework/Versions/3.7/bin:/Library/Frameworks/Python.framework/Versions/3.9/bin:/Library/Frameworks/Python.framework/Versions/3.8/bin:/Users/christian/.deno/bin:/Users/christian/.langflow/uv:/Users/christian/.pyenv/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/usr/local/MacGPG2/bin:/usr/local/go/bin:/Library/Frameworks/Mono.framework/Versions/Current/Commands:/Applications/quarto/bin:/Users/christian/.wasmedge/bin:/Users/christian/.cargo/bin:/Users/christian/.local/bin:/usr/local/bin:/Users/christian/.orbstack/bin', 'PROCESS_LAUNCHED_BY_Q': '1', 'LOGNAME': 'christian', 'TERM': 'xterm-color', 'OSLogRateLimit': '64', 'XPC_FLAGS': '0x0', '__CF_USER_TEXT_ENCODING': '0x1F5:0x0:0x0', 'LC_CTYPE': 'UTF-8', 'JEDITERM_SOURCE': '/Users/christian/IdeaProjects/AEONlib/.venv/bin/activate', 'FIG_TERM': '1', 'SSH_AUTH_SOCK': '/var/run/com.apple.launchd.sz8hMP8NFm/Listeners', 'CLAUDE_CODE_SSE_PORT': '65516', 'XPC_SERVICE_NAME': '0', 'USER': 'christian', 'TERMINAL_EMULATOR': 'JetBrains-JediTerm', 'PROCESS_LAUNCHED_BY_CW': '1', 'SHLVL': '1', 'PWD': '/Users/christian/IdeaProjects/AEONlib', 'OLDPWD': '/Users/christian/IdeaProjects/AEONlib', 'DYLD_LIBRARY_PATH': '/Users/christian/.wasmedge/lib', 'LIBRARY_PATH': '/Users/christian/.wasmedge/lib', 'C_INCLUDE_PATH': '/Users/christian/.wasmedge/include', 'CPLUS_INCLUDE_PATH': '/Users/christian/.wasmedge/include', 'LANG': 'C.UTF-8', 'PYENV_ROOT': '/Users/christian/.pyenv', 'ZSH': '/Users/christian/.oh-my-zsh', 'PAGER': 'cat', 'LESS': '-R', 'LSCOLORS': 'Gxfxcxdxbxegedabagacad', 'LS_COLORS': 'di=1;36:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43:su=30;41:sg=30;46:tw=30;42:ow=30;43', 'VIRTUAL_ENV_DISABLE_PROMPT': '1', 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home', 'NVM_DIR': '/Users/christian/.nvm', 'NVM_CD_FLAGS': '-q', 'PYENV_SHELL': 'zsh', 'BUN_INSTALL': '/Users/christian/.bun', 'VIRTUAL_ENV': '/Users/christian/IdeaProjects/AEONlib/.venv', 'VIRTUAL_ENV_PROMPT': 'aeonlib', '_': '/Users/christian/IdeaProjects/AEONlib/.venv/bin/jupyter', 'JPY_SESSION_NAME': '/Users/christian/IdeaProjects/AEONlib/examples/SALT.ipynb', 'JPY_PARENT_PID': '82763', 'PYDEVD_USE_FRAME_EVAL': 'NO', 'CLICOLOR': '1', 'FORCE_COLOR': '1', 'CLICOLOR_FORCE': '1', 'GIT_PAGER': 'cat', 'MPLBACKEND': 'module://matplotlib_inline.backend_inline', 'SALT_USERNAME': 'ariba', 'SALT_PASSWORD': 'lookingup'})\n" + ] + }, + { + "ename": "ValueError", + "evalue": "salt_username is not set.", + "output_type": "error", + "traceback": [ + "\u001B[31m---------------------------------------------------------------------------\u001B[39m", + "\u001B[31mValueError\u001B[39m Traceback (most recent call last)", + "\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[3]\u001B[39m\u001B[32m, line 3\u001B[39m\n\u001B[32m 1\u001B[39m print(os.environ)\n\u001B[32m 2\u001B[39m \n\u001B[32m----> \u001B[39m\u001B[32m3\u001B[39m facility = SALTFacility(use_playground=\u001B[38;5;28;01mTrue\u001B[39;00m)\n\u001B[32m 4\u001B[39m \n\u001B[32m 5\u001B[39m valid, errors = facility.validate(request)\n\u001B[32m 6\u001B[39m \n", + "\u001B[36mFile \u001B[39m\u001B[32m~/IdeaProjects/AEONlib/src/aeonlib/salt/facility.py:28\u001B[39m, in \u001B[36mSALTFacility.__init__\u001B[39m\u001B[34m(self, use_playground)\u001B[39m\n\u001B[32m 26\u001B[39m username = settings.salt_username\n\u001B[32m 27\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m username:\n\u001B[32m---> \u001B[39m\u001B[32m28\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mValueError\u001B[39;00m(\u001B[33m\"\u001B[39m\u001B[33msalt_username is not set.\u001B[39m\u001B[33m\"\u001B[39m)\n\u001B[32m 29\u001B[39m password = settings.salt_password\n\u001B[32m 30\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m password:\n", + "\u001B[31mValueError\u001B[39m: salt_username is not set." + ] + } + ], + "source": [ + "facility = SALTFacility(use_playground=True)\n", + "\n", + "valid, errors = facility.validate(request)\n", + "\n", + "if valid:\n", + " print(\"The observation request is valid.\")\n", + "else:\n", + " print(\"Validation of the observation request failed with the following error(s):\")\n", + " for error in errors:\n", + " print(error)" + ] + }, + { + "cell_type": "markdown", + "id": "738d55448112708e", + "metadata": {}, + "source": [ + "## Submit the observation request\n", + "\n", + "The observation request can be submitted with the `submit` method of the SALT facility.\n", + "\n", + "This method returns a `pyastrosalt.submission.Submission` object, which you can use to track the submission progress." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "eedbe7ac27488fc4", + "metadata": { + "ExecuteTime": { + "end_time": "2026-05-07T09:40:31.545105Z", + "start_time": "2026-05-07T09:40:31.509820Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'facility' is not defined", + "output_type": "error", + "traceback": [ + "\u001B[31m---------------------------------------------------------------------------\u001B[39m", + "\u001B[31mNameError\u001B[39m Traceback (most recent call last)", + "\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[7]\u001B[39m\u001B[32m, line 1\u001B[39m\n\u001B[32m----> \u001B[39m\u001B[32m1\u001B[39m submission = facility.submit(request)\n\u001B[32m 2\u001B[39m \n\u001B[32m 3\u001B[39m shown_log_messages = \u001B[32m0\u001B[39m\n\u001B[32m 4\u001B[39m \u001B[38;5;28;01mwhile\u001B[39;00m submission.status == SubmissionStatus.IN_PROGRESS:\n", + "\u001B[31mNameError\u001B[39m: name 'facility' is not defined" + ] + } + ], + "source": [ + "submission = facility.submit(request)\n", + "\n", + "shown_log_messages = 0\n", + "while submission.status == SubmissionStatus.IN_PROGRESS:\n", + " sleep(1)\n", + " log_entries = submission.log\n", + " for entry in log_entries[shown_log_messages:]:\n", + " print(f\"[{str(entry.message_type)}] {entry.message}\")\n", + " shown_log_messages = len(log_entries)\n", + "\n", + "if submission.status == SubmissionStatus.SUCCESS:\n", + " print(\n", + " f\"The submission was successful. The proposal code is {submission.proposal_code}.\"\n", + " )\n", + "else:\n", + " print(\"The submission failed with the following error message:\")\n", + " print(submission.error)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From bd50a5b69ebd9916afa232b2a09da08168dfd102 Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 7 May 2026 13:10:25 +0200 Subject: [PATCH 169/171] Update the uv lock file --- uv.lock | 874 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 503 insertions(+), 371 deletions(-) diff --git a/uv.lock b/uv.lock index b529e5e..17ce2b2 100644 --- a/uv.lock +++ b/uv.lock @@ -22,6 +22,12 @@ lt = [ { name = "lxml-stubs" }, { name = "suds" }, ] +salt = [ + { name = "beautifulsoup4" }, + { name = "jinja2" }, + { name = "lxml" }, + { name = "pyastrosalt" }, +] [package.dev-dependencies] codegen = [ @@ -29,28 +35,38 @@ codegen = [ { name = "textcase" }, ] dev = [ + { name = "lxml-stubs" }, { name = "pytest" }, + { name = "time-machine" }, ] [package.metadata] requires-dist = [ { name = "astropy", specifier = ">=6.0" }, + { name = "beautifulsoup4", marker = "extra == 'salt'", specifier = ">=4.14.3" }, { name = "httpx", specifier = ">=0.28.1" }, + { name = "jinja2", marker = "extra == 'salt'", specifier = ">=3.1.6" }, { name = "lxml", marker = "extra == 'lt'", specifier = ">=5.4.0" }, + { name = "lxml", marker = "extra == 'salt'", specifier = ">=6.0.1" }, { name = "lxml-stubs", marker = "extra == 'lt'", specifier = ">=0.5.1" }, { name = "p2api", marker = "extra == 'eso'", specifier = ">=1.0.10" }, + { name = "pyastrosalt", marker = "extra == 'salt'", specifier = ">=0.2.2" }, { name = "pydantic", specifier = ">=2.11.1" }, { name = "pydantic-settings", specifier = ">=2.9.1" }, { name = "suds", marker = "extra == 'lt'", specifier = ">=1.2.0" }, ] -provides-extras = ["eso", "lt"] +provides-extras = ["eso", "lt", "salt"] [package.metadata.requires-dev] codegen = [ { name = "jinja2", specifier = ">=3.1.6" }, { name = "textcase", specifier = ">=0.2.1" }, ] -dev = [{ name = "pytest", specifier = ">=8.3.5" }] +dev = [ + { name = "lxml-stubs", specifier = ">=0.5.1" }, + { name = "pytest", specifier = ">=8.3.5" }, + { name = "time-machine", specifier = ">=3.2.0" }, +] [[package]] name = "annotated-types" @@ -63,21 +79,20 @@ wheels = [ [[package]] name = "anyio" -version = "4.11.0" +version = "4.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, - { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, ] [[package]] name = "astropy" -version = "7.1.1" +version = "7.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astropy-iers-data" }, @@ -86,47 +101,47 @@ dependencies = [ { name = "pyerfa" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e1/6b8846dabc54b6fdc0262adefa041259f52ece9c929b10e0e90937691345/astropy-7.1.1.tar.gz", hash = "sha256:6d128f0005e2c34f70113484468bf9d0e4ca1ee15a279cfd08bdd979d38db0f8", size = 6982773, upload-time = "2025-10-10T20:36:49.347Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/d5/11031eb9788d35826ef527260cf17d5d6ebe8995ba8d67484c236644ce1e/astropy-7.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:268c6bdfb1c4feef8461070bbfdd6b8c07a9badf91977623168d7d3f0ddaf70c", size = 6403108, upload-time = "2025-10-10T20:36:12.958Z" }, - { url = "https://files.pythonhosted.org/packages/01/ca/57d8eeb6f8f67fb3063d6be1e043920f4f25f8e261042fa47a4ff5764c74/astropy-7.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48d10b23e5a53afccd3e092d0c78792f8c644197ece4a7d95d83d7e491768d4c", size = 6349935, upload-time = "2025-10-10T20:36:14.67Z" }, - { url = "https://files.pythonhosted.org/packages/9b/d0/d9d33e9cdc10010e3e2f1e30cb9748a77c3a5ca69d4f4fed82d03bcafd79/astropy-7.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f04b4bdef1990e0b4a5cbdd7871ff172e291b9d4ac27a411e240727e4a357616", size = 10233406, upload-time = "2025-10-10T20:36:16.296Z" }, - { url = "https://files.pythonhosted.org/packages/93/9b/14fb6cf65bd18015c6fdab7c71e4ae00318dd325d0ed03441ff2bb2b913a/astropy-7.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31233556969351c68e104700dbb81e86b02447beeea70028d2d04cd9c2fedd6f", size = 10290578, upload-time = "2025-10-10T20:36:18.595Z" }, - { url = "https://files.pythonhosted.org/packages/c5/1b/994b207601d062f31e67cabbc5827e42b8472ce926ed865f06dba8648429/astropy-7.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:70c69c9a8585d51658837a5643ed9cb4d386f343e2097bce38aba68fbdd48a7f", size = 10228861, upload-time = "2025-10-10T20:36:20.56Z" }, - { url = "https://files.pythonhosted.org/packages/2d/bd/84845404ec729f6e54a94d3f150e5d6c8808dae232f5e12262a72a428c95/astropy-7.1.1-cp312-cp312-win32.whl", hash = "sha256:452be62a2b9f68207f949089937574057415937e5273c37bdaafab0835c21259", size = 6157841, upload-time = "2025-10-10T20:36:22.313Z" }, - { url = "https://files.pythonhosted.org/packages/c1/83/80eca357b28d827f58c688b6c4e3ead88b577af55211676f3e1c13c5bfbd/astropy-7.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:85595f9dce107901ccd3bf493899c4b08a0925abb75d32c72325c4aba5369bd2", size = 6287394, upload-time = "2025-10-10T20:36:24.103Z" }, - { url = "https://files.pythonhosted.org/packages/12/31/77eb5e630da8df9eb30bf9b8234926dd229466cbff01b08590448b3941ae/astropy-7.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:91b8e62cf97d532be28cc794ffe33a394e04e53af24586cea209362aec9ecea1", size = 6397035, upload-time = "2025-10-10T20:36:25.425Z" }, - { url = "https://files.pythonhosted.org/packages/6d/18/af991251dc2f84b1e86b2ec1aa7c77831d90962a69668bba75402da9ad68/astropy-7.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c0c587ff3eab76474e696917273242d434c11de3d729971598b773cf15ef8ff0", size = 6344625, upload-time = "2025-10-10T20:36:26.785Z" }, - { url = "https://files.pythonhosted.org/packages/b9/aa/afd0e18e74fa116469c0ef9aed4c214053412786466f1a1a857af086c616/astropy-7.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc05407cf0ac68896308bf974bad506f1357821b755d0cb60b87b3a1655d96c5", size = 10165691, upload-time = "2025-10-10T20:36:28.73Z" }, - { url = "https://files.pythonhosted.org/packages/0e/37/0fd95850cc52939498b861501bef8d7dc496c2ec6217c1b85a3d2c34940a/astropy-7.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed17a4895edf118e1348560de9deb0596596a41f33314c0d05edce02eb3d1e8", size = 10226235, upload-time = "2025-10-10T20:36:30.915Z" }, - { url = "https://files.pythonhosted.org/packages/d9/df/4539355f72f240de8498494757ba9ddd8562aca83670c39820b917f616dc/astropy-7.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4dcf39226ecad586a11d19207a8a236a3bb0e0412c06574b90ed8799ef202b8", size = 10165501, upload-time = "2025-10-10T20:36:32.588Z" }, - { url = "https://files.pythonhosted.org/packages/1a/46/d881221dcf7f55a3cd945ad55daf994866b4622e796eb27ad59f144568f9/astropy-7.1.1-cp313-cp313-win32.whl", hash = "sha256:33c359bceaa13758be066f878c37d226e899a4faa8cea2711bf68dbbc4a1cb0f", size = 6156145, upload-time = "2025-10-10T20:36:34.299Z" }, - { url = "https://files.pythonhosted.org/packages/45/58/ba5dad5c22a5338ae2954cc81e895add48f8c9b07961a5842cbc2e1f62f2/astropy-7.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:0a3d70df518cb7f400f4ec496b2cbba887c0a0c596fbb15e9bda4fcba07c3f59", size = 6285781, upload-time = "2025-10-10T20:36:35.779Z" }, - { url = "https://files.pythonhosted.org/packages/e9/0e/97a0f1fd764157d3e5aa540a3ae8e9019af4b1278bd1eed69557839fbe61/astropy-7.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1269d03973a14fa8bc16aa7112b1d39953d00ff1ec3ae42677869a7a48395a06", size = 6397841, upload-time = "2025-10-10T20:36:37.721Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ec/c348ebee630e2ea86755ec101e01dca1fa9269e192dc09e24c9b32982d2d/astropy-7.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d9efc15b02b642fd3c4693855b3c8472ac01433a5a441acee97d2e1f586bb4f8", size = 6348050, upload-time = "2025-10-10T20:36:39.56Z" }, - { url = "https://files.pythonhosted.org/packages/9f/65/60bc3e3cc172547e095ebeb82baec4ac55c0e7a2b0553258dea2e5bf74b7/astropy-7.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:43d6894c720c46ffd8ad994edf0b90059fe7a3d71809efef29814c4c3303d710", size = 10154677, upload-time = "2025-10-10T20:36:41.587Z" }, - { url = "https://files.pythonhosted.org/packages/ac/14/ce8df1d39217c85776fb8b053b53fbec8fd027aeb3e7fd52b9fae46e77e7/astropy-7.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:143af6040723919468aa26a0803e0dd57bb340eacebb1cdc5cc56c5a8ace3a42", size = 10179943, upload-time = "2025-10-10T20:36:43.192Z" }, - { url = "https://files.pythonhosted.org/packages/0d/84/7f46819c8ed6590ac8f1cab1954af89cdac44809a51d3e79c85ede0a2191/astropy-7.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c4a69ced2f114c25ee0430ff21b94f4065b123922f26179b5201533aca2b16e", size = 10127907, upload-time = "2025-10-10T20:36:45.03Z" }, - { url = "https://files.pythonhosted.org/packages/16/01/c5024f607af20d6e452ecf9c526a7d6a18895cddb0d3d97e3ecaeb0af621/astropy-7.1.1-cp314-cp314-win32.whl", hash = "sha256:4fd130522cc35d87c4d602b08b6022ca323b1bb00922d02aa39853f1f1f4f234", size = 6173122, upload-time = "2025-10-10T20:36:46.74Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a7/e35633fadb45b21b651a29ed77e7c9531b782e90bc519494a448a82698d7/astropy-7.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:d56b83a7e6757ba4936b4bc73284a2ac4bdf94d7b428f7b6ce8829e4a48c37e9", size = 6306980, upload-time = "2025-10-10T20:36:48.036Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/7b/92/2dce2d48347efc3346d08ca7995b152d242ebd170c571f7c9346468d8427/astropy-7.2.0.tar.gz", hash = "sha256:ae48bc26b1feaeb603cd94bd1fa1aa39137a115fe931b7f13787ab420e8c3070", size = 7057774, upload-time = "2025-11-25T22:36:41.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/6d/6330a844bad8dfc4875e0f2fa1db1fee87837ba9805aa8a8d048c071363a/astropy-7.2.0-cp311-abi3-macosx_10_9_x86_64.whl", hash = "sha256:efac04df4cc488efe630c2fff1992d6516dfb16a06e197fb68bc9e8e3b85def1", size = 6442332, upload-time = "2025-11-25T22:36:23.6Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ba/3418133ba144dfcd1530bca5a6b695f4cdd21a8abaaa2ac4e5450d11b028/astropy-7.2.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:52e9a7d9c86b21f1af911a2930cd0c4a275fb302d455c89e11eedaffef6f2ad0", size = 6413656, upload-time = "2025-11-25T22:36:26.548Z" }, + { url = "https://files.pythonhosted.org/packages/be/ba/05e43b5a7d738316a097fa78524d3eaaff5986294b4a052d4adb3c45e7c0/astropy-7.2.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97c370421b9bb13d4c762c7af06d172bad7c01bd5bcf88314f6913c3c235b770", size = 9758867, upload-time = "2025-11-25T22:36:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1c/f06ad85180e7dd9855aa5ede901bfc2be858d7bee17d4e978a14c0ecec14/astropy-7.2.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f39ce2c80211fbceb005d377a5478cd0d66c42aa1498d252f2239fe5a025c24", size = 9789007, upload-time = "2025-11-25T22:36:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fb/e4d35194a5009d7a73333079481a4ef1380a255d67b9c1db578151a5fb50/astropy-7.2.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ad4d71db994d45f046a1a5449000cf0f88ab6367cb67658500654a0586d6ab19", size = 9748547, upload-time = "2025-11-25T22:36:33.154Z" }, + { url = "https://files.pythonhosted.org/packages/36/ea/f990730978ae0a7a34705f885d2f3806928c5f0bc22eefd6a1a23539cc32/astropy-7.2.0-cp311-abi3-win32.whl", hash = "sha256:95161f26602433176483e8bde8ab1a8ca09148f5b4bf5190569a26d381091598", size = 6237228, upload-time = "2025-11-25T22:36:35.236Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bc/f4378f586dd63902c37d16f68f35f7d555b3b32e08ac6b1d633eb0a48805/astropy-7.2.0-cp311-abi3-win_amd64.whl", hash = "sha256:dc7c340ba1713e55c93071b32033f3153470a0f663a4d539c03a7c9b44020790", size = 6362868, upload-time = "2025-11-25T22:36:37.784Z" }, + { url = "https://files.pythonhosted.org/packages/77/79/b6d4bf01913cfd4ce0cd4c1be5916beccdb92b2970bab8c827984231eae6/astropy-7.2.0-cp311-abi3-win_arm64.whl", hash = "sha256:0c428735a3f15b05c2095bc6ccb5f98a64bc99fb7015866af19ff8492420ddaf", size = 6221756, upload-time = "2025-11-25T22:36:39.852Z" }, ] [[package]] name = "astropy-iers-data" -version = "0.2025.10.20.0.39.8" +version = "0.2026.5.4.1.4.54" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/1c/1eaeac4da97849c5486211384bfb688dff640d29ea1d2c63db32bf43fe54/astropy_iers_data-0.2026.5.4.1.4.54.tar.gz", hash = "sha256:74b70c810e430c274ff9f0c91680d56f7703add8c367c842902dd53130f70832", size = 1932473, upload-time = "2026-05-04T01:05:39.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/f8/077fbdeb102d298dd4e6b9ad024881156c4bbd96e60eeeb13e7d7487448f/astropy_iers_data-0.2026.5.4.1.4.54-py3-none-any.whl", hash = "sha256:9d9014f530020fec2da8de29d5ada776b95cf3616cfac41311853f9f753d1928", size = 1989192, upload-time = "2026-05-04T01:05:36.699Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b1/b4/e7d6623a9f1f43956b56dcca9c03f99e5c4b54d22a7bb9576c253a4b077c/astropy_iers_data-0.2025.10.20.0.39.8.tar.gz", hash = "sha256:ba292db8d5cff8d7a1b16793dc23c2016d7f6c4355c8002925e454d9af2c1938", size = 1911902, upload-time = "2025-10-20T00:39:59.948Z" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/a1/62e43ca7b686f8f1060f30b258a4feed8b2da824b68dff3d90a3ecdd8204/astropy_iers_data-0.2025.10.20.0.39.8-py3-none-any.whl", hash = "sha256:13d8b30b4a65a7a6b60497f5ec263be3b5123321ca8a0333807d5487f7938c3f", size = 1967859, upload-time = "2025-10-20T00:39:58.295Z" }, + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] [[package]] name = "certifi" -version = "2025.10.5" +version = "2026.4.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, ] [[package]] @@ -168,59 +183,75 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] @@ -234,46 +265,55 @@ wheels = [ [[package]] name = "cryptography" -version = "46.0.3" +version = "48.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, - { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, - { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, - { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, - { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, - { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, + { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, + { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, + { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, + { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, + { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, + { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, + { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, + { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, + { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, + { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, + { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] [[package]] @@ -315,11 +355,11 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, ] [[package]] @@ -345,23 +385,23 @@ wheels = [ [[package]] name = "jaraco-context" -version = "6.0.1" +version = "6.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912, upload-time = "2024-08-20T03:39:27.358Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825, upload-time = "2024-08-20T03:39:25.966Z" }, + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, ] [[package]] name = "jaraco-functools" -version = "4.3.0" +version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/ed/1aa2d585304ec07262e1a83a9889880701079dde796ac7b1d1826f40c63d/jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294", size = 19755, upload-time = "2025-08-18T20:05:09.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", size = 10408, upload-time = "2025-08-18T20:05:08.69Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, ] [[package]] @@ -387,7 +427,7 @@ wheels = [ [[package]] name = "keyring" -version = "25.6.0" +version = "25.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jaraco-classes" }, @@ -397,89 +437,89 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750, upload-time = "2024-12-25T15:26:45.782Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, ] [[package]] name = "lxml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, - { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, - { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, - { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, - { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, - { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, - { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, - { url = "https://files.pythonhosted.org/packages/53/fd/4e8f0540608977aea078bf6d79f128e0e2c2bba8af1acf775c30baa70460/lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77", size = 8648494, upload-time = "2025-09-22T04:01:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f4/2a94a3d3dfd6c6b433501b8d470a1960a20ecce93245cf2db1706adf6c19/lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f", size = 4661146, upload-time = "2025-09-22T04:01:56.282Z" }, - { url = "https://files.pythonhosted.org/packages/25/2e/4efa677fa6b322013035d38016f6ae859d06cac67437ca7dc708a6af7028/lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452", size = 4946932, upload-time = "2025-09-22T04:01:58.989Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0f/526e78a6d38d109fdbaa5049c62e1d32fdd70c75fb61c4eadf3045d3d124/lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048", size = 5100060, upload-time = "2025-09-22T04:02:00.812Z" }, - { url = "https://files.pythonhosted.org/packages/81/76/99de58d81fa702cc0ea7edae4f4640416c2062813a00ff24bd70ac1d9c9b/lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df", size = 5019000, upload-time = "2025-09-22T04:02:02.671Z" }, - { url = "https://files.pythonhosted.org/packages/b5/35/9e57d25482bc9a9882cb0037fdb9cc18f4b79d85df94fa9d2a89562f1d25/lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1", size = 5348496, upload-time = "2025-09-22T04:02:04.904Z" }, - { url = "https://files.pythonhosted.org/packages/a6/8e/cb99bd0b83ccc3e8f0f528e9aa1f7a9965dfec08c617070c5db8d63a87ce/lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916", size = 5643779, upload-time = "2025-09-22T04:02:06.689Z" }, - { url = "https://files.pythonhosted.org/packages/d0/34/9e591954939276bb679b73773836c6684c22e56d05980e31d52a9a8deb18/lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd", size = 5244072, upload-time = "2025-09-22T04:02:08.587Z" }, - { url = "https://files.pythonhosted.org/packages/8d/27/b29ff065f9aaca443ee377aff699714fcbffb371b4fce5ac4ca759e436d5/lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6", size = 4718675, upload-time = "2025-09-22T04:02:10.783Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f756f9c2cd27caa1a6ef8c32ae47aadea697f5c2c6d07b0dae133c244fbe/lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a", size = 5255171, upload-time = "2025-09-22T04:02:12.631Z" }, - { url = "https://files.pythonhosted.org/packages/61/46/bb85ea42d2cb1bd8395484fd72f38e3389611aa496ac7772da9205bbda0e/lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679", size = 5057175, upload-time = "2025-09-22T04:02:14.718Z" }, - { url = "https://files.pythonhosted.org/packages/95/0c/443fc476dcc8e41577f0af70458c50fe299a97bb6b7505bb1ae09aa7f9ac/lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659", size = 4785688, upload-time = "2025-09-22T04:02:16.957Z" }, - { url = "https://files.pythonhosted.org/packages/48/78/6ef0b359d45bb9697bc5a626e1992fa5d27aa3f8004b137b2314793b50a0/lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484", size = 5660655, upload-time = "2025-09-22T04:02:18.815Z" }, - { url = "https://files.pythonhosted.org/packages/ff/ea/e1d33808f386bc1339d08c0dcada6e4712d4ed8e93fcad5f057070b7988a/lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2", size = 5247695, upload-time = "2025-09-22T04:02:20.593Z" }, - { url = "https://files.pythonhosted.org/packages/4f/47/eba75dfd8183673725255247a603b4ad606f4ae657b60c6c145b381697da/lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314", size = 5269841, upload-time = "2025-09-22T04:02:22.489Z" }, - { url = "https://files.pythonhosted.org/packages/76/04/5c5e2b8577bc936e219becb2e98cdb1aca14a4921a12995b9d0c523502ae/lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2", size = 3610700, upload-time = "2025-09-22T04:02:24.465Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0a/4643ccc6bb8b143e9f9640aa54e38255f9d3b45feb2cbe7ae2ca47e8782e/lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7", size = 4010347, upload-time = "2025-09-22T04:02:26.286Z" }, - { url = "https://files.pythonhosted.org/packages/31/ef/dcf1d29c3f530577f61e5fe2f1bd72929acf779953668a8a47a479ae6f26/lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf", size = 3671248, upload-time = "2025-09-22T04:02:27.918Z" }, - { url = "https://files.pythonhosted.org/packages/03/15/d4a377b385ab693ce97b472fe0c77c2b16ec79590e688b3ccc71fba19884/lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe", size = 8659801, upload-time = "2025-09-22T04:02:30.113Z" }, - { url = "https://files.pythonhosted.org/packages/c8/e8/c128e37589463668794d503afaeb003987373c5f94d667124ffd8078bbd9/lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d", size = 4659403, upload-time = "2025-09-22T04:02:32.119Z" }, - { url = "https://files.pythonhosted.org/packages/00/ce/74903904339decdf7da7847bb5741fc98a5451b42fc419a86c0c13d26fe2/lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d", size = 4966974, upload-time = "2025-09-22T04:02:34.155Z" }, - { url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5", size = 5102953, upload-time = "2025-09-22T04:02:36.054Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ea/a43ba9bb750d4ffdd885f2cd333572f5bb900cd2408b67fdda07e85978a0/lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0", size = 5055054, upload-time = "2025-09-22T04:02:38.154Z" }, - { url = "https://files.pythonhosted.org/packages/60/23/6885b451636ae286c34628f70a7ed1fcc759f8d9ad382d132e1c8d3d9bfd/lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba", size = 5352421, upload-time = "2025-09-22T04:02:40.413Z" }, - { url = "https://files.pythonhosted.org/packages/48/5b/fc2ddfc94ddbe3eebb8e9af6e3fd65e2feba4967f6a4e9683875c394c2d8/lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0", size = 5673684, upload-time = "2025-09-22T04:02:42.288Z" }, - { url = "https://files.pythonhosted.org/packages/29/9c/47293c58cc91769130fbf85531280e8cc7868f7fbb6d92f4670071b9cb3e/lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d", size = 5252463, upload-time = "2025-09-22T04:02:44.165Z" }, - { url = "https://files.pythonhosted.org/packages/9b/da/ba6eceb830c762b48e711ded880d7e3e89fc6c7323e587c36540b6b23c6b/lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37", size = 4698437, upload-time = "2025-09-22T04:02:46.524Z" }, - { url = "https://files.pythonhosted.org/packages/a5/24/7be3f82cb7990b89118d944b619e53c656c97dc89c28cfb143fdb7cd6f4d/lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9", size = 5269890, upload-time = "2025-09-22T04:02:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/1b/bd/dcfb9ea1e16c665efd7538fc5d5c34071276ce9220e234217682e7d2c4a5/lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917", size = 5097185, upload-time = "2025-09-22T04:02:50.746Z" }, - { url = "https://files.pythonhosted.org/packages/21/04/a60b0ff9314736316f28316b694bccbbabe100f8483ad83852d77fc7468e/lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f", size = 4745895, upload-time = "2025-09-22T04:02:52.968Z" }, - { url = "https://files.pythonhosted.org/packages/d6/bd/7d54bd1846e5a310d9c715921c5faa71cf5c0853372adf78aee70c8d7aa2/lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8", size = 5695246, upload-time = "2025-09-22T04:02:54.798Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/5643d6ab947bc371da21323acb2a6e603cedbe71cb4c99c8254289ab6f4e/lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a", size = 5260797, upload-time = "2025-09-22T04:02:57.058Z" }, - { url = "https://files.pythonhosted.org/packages/33/da/34c1ec4cff1eea7d0b4cd44af8411806ed943141804ac9c5d565302afb78/lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c", size = 5277404, upload-time = "2025-09-22T04:02:58.966Z" }, - { url = "https://files.pythonhosted.org/packages/82/57/4eca3e31e54dc89e2c3507e1cd411074a17565fa5ffc437c4ae0a00d439e/lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b", size = 3670072, upload-time = "2025-09-22T04:03:38.05Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e0/c96cf13eccd20c9421ba910304dae0f619724dcf1702864fd59dd386404d/lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed", size = 4080617, upload-time = "2025-09-22T04:03:39.835Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5d/b3f03e22b3d38d6f188ef044900a9b29b2fe0aebb94625ce9fe244011d34/lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8", size = 3754930, upload-time = "2025-09-22T04:03:41.565Z" }, - { url = "https://files.pythonhosted.org/packages/5e/5c/42c2c4c03554580708fc738d13414801f340c04c3eff90d8d2d227145275/lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d", size = 8910380, upload-time = "2025-09-22T04:03:01.645Z" }, - { url = "https://files.pythonhosted.org/packages/bf/4f/12df843e3e10d18d468a7557058f8d3733e8b6e12401f30b1ef29360740f/lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba", size = 4775632, upload-time = "2025-09-22T04:03:03.814Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0c/9dc31e6c2d0d418483cbcb469d1f5a582a1cd00a1f4081953d44051f3c50/lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601", size = 4975171, upload-time = "2025-09-22T04:03:05.651Z" }, - { url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed", size = 5110109, upload-time = "2025-09-22T04:03:07.452Z" }, - { url = "https://files.pythonhosted.org/packages/bf/0c/4f5f2a4dd319a178912751564471355d9019e220c20d7db3fb8307ed8582/lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37", size = 5041061, upload-time = "2025-09-22T04:03:09.297Z" }, - { url = "https://files.pythonhosted.org/packages/12/64/554eed290365267671fe001a20d72d14f468ae4e6acef1e179b039436967/lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338", size = 5306233, upload-time = "2025-09-22T04:03:11.651Z" }, - { url = "https://files.pythonhosted.org/packages/7a/31/1d748aa275e71802ad9722df32a7a35034246b42c0ecdd8235412c3396ef/lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9", size = 5604739, upload-time = "2025-09-22T04:03:13.592Z" }, - { url = "https://files.pythonhosted.org/packages/8f/41/2c11916bcac09ed561adccacceaedd2bf0e0b25b297ea92aab99fd03d0fa/lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd", size = 5225119, upload-time = "2025-09-22T04:03:15.408Z" }, - { url = "https://files.pythonhosted.org/packages/99/05/4e5c2873d8f17aa018e6afde417c80cc5d0c33be4854cce3ef5670c49367/lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d", size = 4633665, upload-time = "2025-09-22T04:03:17.262Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c9/dcc2da1bebd6275cdc723b515f93edf548b82f36a5458cca3578bc899332/lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9", size = 5234997, upload-time = "2025-09-22T04:03:19.14Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e2/5172e4e7468afca64a37b81dba152fc5d90e30f9c83c7c3213d6a02a5ce4/lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e", size = 5090957, upload-time = "2025-09-22T04:03:21.436Z" }, - { url = "https://files.pythonhosted.org/packages/a5/b3/15461fd3e5cd4ddcb7938b87fc20b14ab113b92312fc97afe65cd7c85de1/lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d", size = 4764372, upload-time = "2025-09-22T04:03:23.27Z" }, - { url = "https://files.pythonhosted.org/packages/05/33/f310b987c8bf9e61c4dd8e8035c416bd3230098f5e3cfa69fc4232de7059/lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec", size = 5634653, upload-time = "2025-09-22T04:03:25.767Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/51c80e75e0bc9382158133bdcf4e339b5886c6ee2418b5199b3f1a61ed6d/lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272", size = 5233795, upload-time = "2025-09-22T04:03:27.62Z" }, - { url = "https://files.pythonhosted.org/packages/56/4d/4856e897df0d588789dd844dbed9d91782c4ef0b327f96ce53c807e13128/lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f", size = 5257023, upload-time = "2025-09-22T04:03:30.056Z" }, - { url = "https://files.pythonhosted.org/packages/0f/85/86766dfebfa87bea0ab78e9ff7a4b4b45225df4b4d3b8cc3c03c5cd68464/lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312", size = 3911420, upload-time = "2025-09-22T04:03:32.198Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1a/b248b355834c8e32614650b8008c69ffeb0ceb149c793961dd8c0b991bb3/lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca", size = 4406837, upload-time = "2025-09-22T04:03:34.027Z" }, - { url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205, upload-time = "2025-09-22T04:03:36.249Z" }, +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/d4/9326838b59dc36dfae42eec9656b97520f9997eee1de47b8316aaeed169c/lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", size = 8570663, upload-time = "2026-04-18T04:27:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a4/053745ce1f8303ccbb788b86c0db3a91b973675cefc42566a188637b7c40/lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93", size = 4624024, upload-time = "2026-04-18T04:27:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/90/97/a517944b20f8fd0932ad2109482bee4e29fe721416387a363306667941f6/lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", size = 4930895, upload-time = "2026-04-18T04:32:56.29Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/e08a970727d556caa040a44773c7b7e3ad0f0d73dedc863543e9a8b931f2/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a", size = 5093820, upload-time = "2026-04-18T04:32:58.94Z" }, + { url = "https://files.pythonhosted.org/packages/88/ee/2a5c2aa2c32016a226ca25d3e1056a8102ea6e1fe308bf50213586635400/lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105", size = 5005790, upload-time = "2026-04-18T04:33:01.272Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/a0db9be8f38ad6043ab9429487c128dd1d30f07956ef43040402f8da49e8/lxml-6.1.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485", size = 5630827, upload-time = "2026-04-18T04:33:04.036Z" }, + { url = "https://files.pythonhosted.org/packages/31/ba/3c13d3fc24b7cacf675f808a3a1baabf43a30d0cd24c98f94548e9aa58eb/lxml-6.1.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814", size = 5240445, upload-time = "2026-04-18T04:33:06.87Z" }, + { url = "https://files.pythonhosted.org/packages/55/ba/eeef4ccba09b2212fe239f46c1692a98db1878e0872ae320756488878a94/lxml-6.1.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32", size = 5350121, upload-time = "2026-04-18T04:33:09.365Z" }, + { url = "https://files.pythonhosted.org/packages/7e/01/1da87c7b587c38d0cbe77a01aae3b9c1c49ed47d76918ef3db8fc151b1ca/lxml-6.1.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad", size = 4694949, upload-time = "2026-04-18T04:33:11.628Z" }, + { url = "https://files.pythonhosted.org/packages/a1/88/7db0fe66d5aaf128443ee1623dec3db1576f3e4c17751ec0ef5866468590/lxml-6.1.0-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54", size = 5243901, upload-time = "2026-04-18T04:33:13.95Z" }, + { url = "https://files.pythonhosted.org/packages/00/a8/1346726af7d1f6fca1f11223ba34001462b0a3660416986d37641708d57c/lxml-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d", size = 5048054, upload-time = "2026-04-18T04:33:16.965Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b7/85057012f035d1a0c87e02f8c723ca3c3e6e0728bcf4cb62080b21b1c1e3/lxml-6.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69", size = 4777324, upload-time = "2026-04-18T04:33:19.832Z" }, + { url = "https://files.pythonhosted.org/packages/75/6c/ad2f94a91073ef570f33718040e8e160d5fb93331cf1ab3ca1323f939e2d/lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d", size = 5645702, upload-time = "2026-04-18T04:33:22.436Z" }, + { url = "https://files.pythonhosted.org/packages/3b/89/0bb6c0bd549c19004c60eea9dc554dd78fd647b72314ef25d460e0d208c6/lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5", size = 5232901, upload-time = "2026-04-18T04:33:26.21Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d9/d609a11fb567da9399f525193e2b49847b5a409cdebe737f06a8b7126bdc/lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d", size = 5261333, upload-time = "2026-04-18T04:33:28.984Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3a/ac3f99ec8ac93089e7dd556f279e0d14c24de0a74a507e143a2e4b496e7c/lxml-6.1.0-cp312-cp312-win32.whl", hash = "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f", size = 3596289, upload-time = "2026-04-18T04:27:42.819Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a7/0a915557538593cb1bbeedcd40e13c7a261822c26fecbbdb71dad0c2f540/lxml-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366", size = 3997059, upload-time = "2026-04-18T04:27:46.764Z" }, + { url = "https://files.pythonhosted.org/packages/92/96/a5dc078cf0126fbfbc35611d77ecd5da80054b5893e28fb213a5613b9e1d/lxml-6.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819", size = 3659552, upload-time = "2026-04-18T04:27:51.133Z" }, + { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, + { url = "https://files.pythonhosted.org/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" }, + { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, + { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, + { url = "https://files.pythonhosted.org/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" }, + { url = "https://files.pythonhosted.org/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578, upload-time = "2026-04-18T04:33:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, + { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" }, + { url = "https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" }, + { url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" }, + { url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522, upload-time = "2026-04-18T04:34:14.89Z" }, + { url = "https://files.pythonhosted.org/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310, upload-time = "2026-04-18T04:34:17.652Z" }, + { url = "https://files.pythonhosted.org/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799, upload-time = "2026-04-18T04:34:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693, upload-time = "2026-04-18T04:34:23.541Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708, upload-time = "2026-04-18T04:34:26.001Z" }, + { url = "https://files.pythonhosted.org/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737, upload-time = "2026-04-18T04:34:28.32Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817, upload-time = "2026-04-18T04:34:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" }, + { url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" }, + { url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" }, + { url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" }, + { url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" }, + { url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" }, + { url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" }, + { url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" }, + { url = "https://files.pythonhosted.org/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008, upload-time = "2026-04-18T04:34:51.503Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451, upload-time = "2026-04-18T04:34:54.263Z" }, + { url = "https://files.pythonhosted.org/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135, upload-time = "2026-04-18T04:34:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126, upload-time = "2026-04-18T04:34:59.704Z" }, + { url = "https://files.pythonhosted.org/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579, upload-time = "2026-04-18T04:35:02.658Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206, upload-time = "2026-04-18T04:35:05.175Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906, upload-time = "2026-04-18T04:35:08.098Z" }, + { url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" }, + { url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" }, + { url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" }, ] [[package]] @@ -556,96 +596,94 @@ wheels = [ [[package]] name = "more-itertools" -version = "10.8.0" +version = "11.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, + { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, ] [[package]] name = "numpy" -version = "2.3.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/7a/02420400b736f84317e759291b8edaeee9dc921f72b045475a9cbdb26b17/numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11", size = 20957727, upload-time = "2025-10-15T16:15:44.9Z" }, - { url = "https://files.pythonhosted.org/packages/18/90/a014805d627aa5750f6f0e878172afb6454552da929144b3c07fcae1bb13/numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9", size = 14187262, upload-time = "2025-10-15T16:15:47.761Z" }, - { url = "https://files.pythonhosted.org/packages/c7/e4/0a94b09abe89e500dc748e7515f21a13e30c5c3fe3396e6d4ac108c25fca/numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667", size = 5115992, upload-time = "2025-10-15T16:15:50.144Z" }, - { url = "https://files.pythonhosted.org/packages/88/dd/db77c75b055c6157cbd4f9c92c4458daef0dd9cbe6d8d2fe7f803cb64c37/numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef", size = 6648672, upload-time = "2025-10-15T16:15:52.442Z" }, - { url = "https://files.pythonhosted.org/packages/e1/e6/e31b0d713719610e406c0ea3ae0d90760465b086da8783e2fd835ad59027/numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e", size = 14284156, upload-time = "2025-10-15T16:15:54.351Z" }, - { url = "https://files.pythonhosted.org/packages/f9/58/30a85127bfee6f108282107caf8e06a1f0cc997cb6b52cdee699276fcce4/numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a", size = 16641271, upload-time = "2025-10-15T16:15:56.67Z" }, - { url = "https://files.pythonhosted.org/packages/06/f2/2e06a0f2adf23e3ae29283ad96959267938d0efd20a2e25353b70065bfec/numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16", size = 16059531, upload-time = "2025-10-15T16:15:59.412Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e7/b106253c7c0d5dc352b9c8fab91afd76a93950998167fa3e5afe4ef3a18f/numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786", size = 18578983, upload-time = "2025-10-15T16:16:01.804Z" }, - { url = "https://files.pythonhosted.org/packages/73/e3/04ecc41e71462276ee867ccbef26a4448638eadecf1bc56772c9ed6d0255/numpy-2.3.4-cp312-cp312-win32.whl", hash = "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc", size = 6291380, upload-time = "2025-10-15T16:16:03.938Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a8/566578b10d8d0e9955b1b6cd5db4e9d4592dd0026a941ff7994cedda030a/numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32", size = 12787999, upload-time = "2025-10-15T16:16:05.801Z" }, - { url = "https://files.pythonhosted.org/packages/58/22/9c903a957d0a8071b607f5b1bff0761d6e608b9a965945411f867d515db1/numpy-2.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db", size = 10197412, upload-time = "2025-10-15T16:16:07.854Z" }, - { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" }, - { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" }, - { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" }, - { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" }, - { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" }, - { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" }, - { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" }, - { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" }, - { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" }, - { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" }, - { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" }, - { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" }, - { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" }, - { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" }, - { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" }, - { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" }, - { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" }, - { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" }, - { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" }, - { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" }, - { url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" }, - { url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" }, - { url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" }, - { url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" }, - { url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" }, - { url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" }, - { url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" }, - { url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" }, - { url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" }, - { url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" }, - { url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" }, - { url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" }, - { url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" }, - { url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" }, - { url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" }, - { url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" }, - { url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" }, - { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" }, +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, ] [[package]] name = "p2api" -version = "1.0.10" +version = "1.0.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "keyring" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/94/96ece6cf22552578a16f82ccedabc1d067f65ba871cfec60324a2eb51d12/p2api-1.0.10.tar.gz", hash = "sha256:9d9d41d978fb5143c9eb1e7119bd8bd7ff1c0f74b267d2fef425e59c717b35ef", size = 25017, upload-time = "2024-09-12T12:56:51.453Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/54/2181f0b2c85c3515d01b8a99fe70bd8e1dc0c673a4f6f1dbd512f26ce441/p2api-1.0.11.tar.gz", hash = "sha256:779fcb6f5502a210eba39e64026348dcbcfe256208afde5d199645d9d2b943d6", size = 24994, upload-time = "2026-02-19T09:41:41.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/a3/07ff4a0be52675076fea31558ed4c4e880887df606bcdd395ac8b2802298/p2api-1.0.10-py2.py3-none-any.whl", hash = "sha256:0d1d1edfbbc9202174147fa34117522db6c27af4b4d3e86adad7458f2df6965b", size = 26400, upload-time = "2024-09-12T12:56:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ce/7158780102f6878279ee358d36f8ad13e45061a0a8c3bf552d37cfe61c22/p2api-1.0.11-py2.py3-none-any.whl", hash = "sha256:8eb1b51c0cee59b3292434dffc36c4b0b34c787b1728aeba73c036d017d8901c", size = 26415, upload-time = "2026-02-19T09:41:40.154Z" }, ] [[package]] name = "packaging" -version = "25.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] @@ -657,18 +695,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pyastrosalt" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "defusedxml" }, + { name = "requests" }, + { name = "types-defusedxml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/1c/27756fc0b4e058fc9f9d6b5246661a285c671e9e8008236a6fecb3e17220/pyastrosalt-0.2.2.tar.gz", hash = "sha256:ae6debbc552f3592260fde1504e59d46ca56dd82400cd59e3a03f9a4b324e637", size = 18419, upload-time = "2026-05-07T09:48:10.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/9f/469888b597ad0017f78228efef10017c9cd7c1f5077c2fa20c9805734dd0/pyastrosalt-0.2.2-py3-none-any.whl", hash = "sha256:7ed089864166b3fd1fe5c79936e2f8f3b547aa696c899bf86d701368a82590e7", size = 14198, upload-time = "2026-05-07T09:48:11.183Z" }, +] + [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] name = "pydantic" -version = "2.12.3" +version = "2.13.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -676,90 +728,98 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, ] [[package]] name = "pydantic-core" -version = "2.41.4" +version = "2.46.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" }, - { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" }, - { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" }, - { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590, upload-time = "2025-10-14T10:20:34.332Z" }, - { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869, upload-time = "2025-10-14T10:20:35.965Z" }, - { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169, upload-time = "2025-10-14T10:20:37.627Z" }, - { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165, upload-time = "2025-10-14T10:20:39.246Z" }, - { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067, upload-time = "2025-10-14T10:20:41.015Z" }, - { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997, upload-time = "2025-10-14T10:20:43.106Z" }, - { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187, upload-time = "2025-10-14T10:20:44.849Z" }, - { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204, upload-time = "2025-10-14T10:20:46.781Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536, upload-time = "2025-10-14T10:20:48.39Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132, upload-time = "2025-10-14T10:20:50.421Z" }, - { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483, upload-time = "2025-10-14T10:20:52.35Z" }, - { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, - { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, - { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, - { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, - { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, - { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, - { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, - { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, - { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, - { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, - { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, - { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, - { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, - { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, - { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, - { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, - { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, - { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" }, - { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" }, - { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" }, - { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" }, - { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" }, - { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" }, - { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" }, - { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" }, - { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" }, - { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" }, - { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, - { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, - { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" }, - { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, ] [[package]] name = "pydantic-settings" -version = "2.11.0" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180", size = 188394, upload-time = "2025-09-24T14:19:11.764Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, ] [[package]] @@ -782,16 +842,16 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -800,18 +860,18 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] name = "python-dotenv" -version = "1.1.1" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] [[package]] @@ -871,7 +931,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.5" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -879,31 +939,31 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] name = "secretstorage" -version = "3.4.0" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "jeepney" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/9f/11ef35cf1027c1339552ea7bfe6aaa74a8516d8b5caf6e7d338daf54fd80/secretstorage-3.4.0.tar.gz", hash = "sha256:c46e216d6815aff8a8a18706a2fbfd8d53fcbb0dce99301881687a1b0289ef7c", size = 19748, upload-time = "2025-09-09T16:42:13.859Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/ff/2e2eed29e02c14a5cb6c57f09b2d5b40e65d6cc71f45b52e0be295ccbc2f/secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e", size = 15272, upload-time = "2025-09-09T16:42:12.744Z" }, + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, ] [[package]] -name = "sniffio" -version = "1.3.1" +name = "soupsieve" +version = "2.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, ] [[package]] @@ -924,6 +984,78 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/48/6ea42c749608cfca883d5d8fae03edbcde636090d2bc751fd5da9157b451/textcase-0.4.5-py3-none-any.whl", hash = "sha256:bd5d6aaf653b339e3ac60ad96cfc960a94219a97da464eddeed7084f41774937", size = 6503, upload-time = "2025-10-10T15:11:24.294Z" }, ] +[[package]] +name = "time-machine" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/fc/37b02f6094dbb1f851145330460532176ed2f1dc70511a35828166c41e52/time_machine-3.2.0.tar.gz", hash = "sha256:a4ddd1cea17b8950e462d1805a42b20c81eb9aafc8f66b392dd5ce997e037d79", size = 14804, upload-time = "2025-12-17T23:33:02.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/8b/080c8eedcd67921a52ba5bd0e075362062509ab63c86fc1a0442fad241a6/time_machine-3.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cc4bee5b0214d7dc4ebc91f4a4c600f1a598e9b5606ac751f42cb6f6740b1dbb", size = 19255, upload-time = "2025-12-17T23:31:58.057Z" }, + { url = "https://files.pythonhosted.org/packages/66/17/0e5291e9eb705bf8a5a1305f826e979af307bbeb79def4ddbf4b3f9a81e0/time_machine-3.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ca036304b4460ae2fdc1b52dd8b1fa7cf1464daa427fc49567413c09aa839c1", size = 15360, upload-time = "2025-12-17T23:31:59.048Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/9ab87b71d2e2b62463b9b058b7ae7ac09fb57f8fcd88729dec169d304340/time_machine-3.2.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5442735b41d7a2abc2f04579b4ca6047ed4698a8338a4fec92c7c9423e7938cb", size = 33029, upload-time = "2025-12-17T23:32:00.413Z" }, + { url = "https://files.pythonhosted.org/packages/4b/26/b5ca19da6f25ea905b3e10a0ea95d697c1aeba0404803a43c68f1af253e6/time_machine-3.2.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:97da3e971e505cb637079fb07ab0bcd36e33279f8ecac888ff131f45ef1e4d8d", size = 34579, upload-time = "2025-12-17T23:32:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/79/ca/6ac7ad5f10ea18cc1d9de49716ba38c32132c7b64532430d92ef240c116b/time_machine-3.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3cdda6dee4966e38aeb487309bb414c6cb23a81fc500291c77a8fcd3098832e7", size = 35961, upload-time = "2025-12-17T23:32:02.521Z" }, + { url = "https://files.pythonhosted.org/packages/33/67/390dd958bed395ab32d79a9fe61fe111825c0dd4ded54dbba7e867f171e6/time_machine-3.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:33d9efd302a6998bcc8baa4d84f259f8a4081105bd3d7f7af7f1d0abd3b1c8aa", size = 34668, upload-time = "2025-12-17T23:32:03.585Z" }, + { url = "https://files.pythonhosted.org/packages/da/57/c88fff034a4e9538b3ae7c68c9cfb283670b14d17522c5a8bc17d29f9a4b/time_machine-3.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3a0b0a33971f14145853c9bd95a6ab0353cf7e0019fa2a7aa1ae9fddfe8eab50", size = 32891, upload-time = "2025-12-17T23:32:04.656Z" }, + { url = "https://files.pythonhosted.org/packages/2d/70/ebbb76022dba0fec8f9156540fc647e4beae1680c787c01b1b6200e56d70/time_machine-3.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2d0be9e5f22c38082d247a2cdcd8a936504e9db60b7b3606855fb39f299e9548", size = 34080, upload-time = "2025-12-17T23:32:06.146Z" }, + { url = "https://files.pythonhosted.org/packages/db/9a/2ca9e7af3df540dc1c79e3de588adeddb7dcc2107829248e6969c4f14167/time_machine-3.2.0-cp312-cp312-win32.whl", hash = "sha256:3f74623648b936fdce5f911caf386c0a0b579456410975de8c0dfeaaffece1d8", size = 17371, upload-time = "2025-12-17T23:32:07.164Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ce/21d23efc9c2151939af1b7ee4e60d86d661b74ef32b8eaa148f6fe8c899c/time_machine-3.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:34e26a41d994b5e4b205136a90e9578470386749cc9a2ecf51ca18f83ce25e23", size = 18132, upload-time = "2025-12-17T23:32:08.447Z" }, + { url = "https://files.pythonhosted.org/packages/2f/34/c2b70be483accf6db9e5d6c3139bce3c38fe51f898ccf64e8d3fe14fbf4d/time_machine-3.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:0615d3d82c418d6293f271c348945c5091a71f37e37173653d5c26d0e74b13a8", size = 16930, upload-time = "2025-12-17T23:32:09.477Z" }, + { url = "https://files.pythonhosted.org/packages/ee/cd/43ad5efc88298af3c59b66769cea7f055567a85071579ed40536188530c1/time_machine-3.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c421a8eb85a4418a7675a41bf8660224318c46cc62e4751c8f1ceca752059090", size = 19318, upload-time = "2025-12-17T23:32:10.518Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f6/084010ef7f4a3f38b5a4900923d7c85b29e797655c4f6ee4ce54d903cca8/time_machine-3.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f4e758f7727d0058c4950c66b58200c187072122d6f7a98b610530a4233ea7b", size = 15390, upload-time = "2025-12-17T23:32:11.625Z" }, + { url = "https://files.pythonhosted.org/packages/25/aa/1cabb74134f492270dc6860cb7865859bf40ecf828be65972827646e91ad/time_machine-3.2.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:154bd3f75c81f70218b2585cc12b60762fb2665c507eec5ec5037d8756d9b4e0", size = 33115, upload-time = "2025-12-17T23:32:13.219Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/78c5d7dfa366924eb4dbfcc3fc917c39a4280ca234b12819cc1f16c03d88/time_machine-3.2.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d50cfe5ebea422c896ad8d278af9648412b7533b8ea6adeeee698a3fd9b1d3b7", size = 34705, upload-time = "2025-12-17T23:32:14.29Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/d5e877c24541f674c6869ff6e9c56833369796010190252e92c9d7ae5f0f/time_machine-3.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:636576501724bd6a9124e69d86e5aef263479e89ef739c5db361469f0463a0a1", size = 36104, upload-time = "2025-12-17T23:32:15.354Z" }, + { url = "https://files.pythonhosted.org/packages/22/1c/d4bae72f388f67efc9609f89b012e434bb19d9549c7a7b47d6c7d9e5c55d/time_machine-3.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40e6f40c57197fcf7ec32d2c563f4df0a82c42cdcc3cab27f688e98f6060df10", size = 34765, upload-time = "2025-12-17T23:32:16.434Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c3/ac378cf301d527d8dfad2f0db6bad0dfb1ab73212eaa56d6b96ee5d9d20b/time_machine-3.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a1bcf0b846bbfc19a79bc19e3fa04d8c7b1e8101c1b70340ffdb689cd801ea53", size = 33010, upload-time = "2025-12-17T23:32:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/06/35/7ce897319accda7a6970b288a9a8c52d25227342a7508505a2b3d235b649/time_machine-3.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae55a56c179f4fe7a62575ad5148b6ed82f6c7e5cf2f9a9ec65f2f5b067db5f5", size = 34185, upload-time = "2025-12-17T23:32:18.566Z" }, + { url = "https://files.pythonhosted.org/packages/bf/28/f922022269749cb02eee2b62919671153c4088994fa955a6b0e50327ff81/time_machine-3.2.0-cp313-cp313-win32.whl", hash = "sha256:a66fe55a107e46916007a391d4030479df8864ec6ad6f6a6528221befc5c886e", size = 17397, upload-time = "2025-12-17T23:32:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/fd87cde397f4a7bea493152f0aca8fd569ec709cad9e0f2ca7011eb8c7f7/time_machine-3.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:30c9ce57165df913e4f74e285a8ab829ff9b7aa3e5ec0973f88f642b9a7b3d15", size = 18139, upload-time = "2025-12-17T23:32:20.991Z" }, + { url = "https://files.pythonhosted.org/packages/75/81/b8ce58233addc5d7d54d2fabc49dcbc02d79e3f079d150aa1bec3d5275ef/time_machine-3.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:89cad7e179e9bdcc84dcf09efe52af232c4cc7a01b3de868356bbd59d95bd9b8", size = 16964, upload-time = "2025-12-17T23:32:22.075Z" }, + { url = "https://files.pythonhosted.org/packages/67/e7/487f0ba5fe6c58186a5e1af2a118dfa2c160fedb37ef53a7e972d410408e/time_machine-3.2.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:59d71545e62525a4b85b6de9ab5c02ee3c61110fd7f636139914a2335dcbfc9c", size = 20000, upload-time = "2025-12-17T23:32:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/e1/17/eb2c0054c8d44dd42df84ccd434539249a9c7d0b8eb53f799be2102500ab/time_machine-3.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:999672c621c35362bc28e03ca0c7df21500195540773c25993421fd8d6cc5003", size = 15657, upload-time = "2025-12-17T23:32:24.125Z" }, + { url = "https://files.pythonhosted.org/packages/43/21/93443b5d1dd850f8bb9442e90d817a9033dcce6bfbdd3aabbb9786251c80/time_machine-3.2.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5faf7397f0580c7b9d67288522c8d7863e85f0cffadc0f1fccdb2c3dfce5783e", size = 39216, upload-time = "2025-12-17T23:32:25.542Z" }, + { url = "https://files.pythonhosted.org/packages/9f/9e/18544cf8acc72bb1dc03762231c82ecc259733f4bb6770a7bbe5cd138603/time_machine-3.2.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3dd886ec49f1fa5a00e844f5947e5c0f98ce574750c24b7424c6f77fc1c3e87", size = 40764, upload-time = "2025-12-17T23:32:26.643Z" }, + { url = "https://files.pythonhosted.org/packages/27/f7/9fe9ce2795636a3a7467307af6bdf38bb613ddb701a8a5cd50ec713beb5e/time_machine-3.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da0ecd96bc7bbe450acaaabe569d84e81688f1be8ad58d1470e42371d145fb53", size = 43526, upload-time = "2025-12-17T23:32:27.693Z" }, + { url = "https://files.pythonhosted.org/packages/03/c1/a93e975ba9dec22e87ec92d18c28e67d36bd536f9119ffa439b2892b0c9c/time_machine-3.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:158220e946c1c4fb8265773a0282c88c35a7e3bb5d78e3561214e3b3231166f3", size = 41727, upload-time = "2025-12-17T23:32:28.985Z" }, + { url = "https://files.pythonhosted.org/packages/5f/fb/e3633e5a6bbed1c76bb2e9810dabc2f8467532ffcd29b9aed404b473061a/time_machine-3.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c1aee29bc54356f248d5d7dfdd131e12ca825e850a08c0ebdb022266d073013", size = 38952, upload-time = "2025-12-17T23:32:30.031Z" }, + { url = "https://files.pythonhosted.org/packages/82/3d/02e9fb2526b3d6b1b45bc8e4d912d95d1cd699d1a3f6df985817d37a0600/time_machine-3.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c8ed2224f09d25b1c2fc98683613aca12f90f682a427eabb68fc824d27014e4a", size = 39829, upload-time = "2025-12-17T23:32:31.075Z" }, + { url = "https://files.pythonhosted.org/packages/85/c8/c14265212436da8e0814c45463987b3f57de3eca4de023cc2eabb0c62ef3/time_machine-3.2.0-cp313-cp313t-win32.whl", hash = "sha256:3498719f8dab51da76d29a20c1b5e52ee7db083dddf3056af7fa69c1b94e1fe6", size = 17852, upload-time = "2025-12-17T23:32:32.079Z" }, + { url = "https://files.pythonhosted.org/packages/1d/bc/8acb13cf6149f47508097b158a9a8bec9ec4530a70cb406124e8023581f5/time_machine-3.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e0d90bee170b219e1d15e6a58164aa808f5170090e4f090bd0670303e34181b1", size = 18918, upload-time = "2025-12-17T23:32:33.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/87/c443ee508c2708fd2514ccce9052f5e48888783ce690506919629ebc8eb0/time_machine-3.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:051de220fdb6e20d648111bbad423d9506fdbb2e44d4429cef3dc0382abf1fc2", size = 17261, upload-time = "2025-12-17T23:32:34.446Z" }, + { url = "https://files.pythonhosted.org/packages/61/70/b4b980d126ed155c78d1879c50d60c8dcbd47bd11cb14ee7be50e0dfc07f/time_machine-3.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1398980c017fe5744d66f419e0115ee48a53b00b146d738e1416c225eb610b82", size = 19303, upload-time = "2025-12-17T23:32:35.796Z" }, + { url = "https://files.pythonhosted.org/packages/73/73/eaa33603c69a68fe2b6f54f9dd75481693d62f1d29676531002be06e2d1c/time_machine-3.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4f8f4e35f4191ef70c2ab8ff490761ee9051b891afce2bf86dde3918eb7b537b", size = 15431, upload-time = "2025-12-17T23:32:37.244Z" }, + { url = "https://files.pythonhosted.org/packages/76/10/b81e138e86cc7bab40cdb59d294b341e172201f4a6c84bb0ec080407977a/time_machine-3.2.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6db498686ecf6163c5aa8cf0bcd57bbe0f4081184f247edf3ee49a2612b584f9", size = 33206, upload-time = "2025-12-17T23:32:38.713Z" }, + { url = "https://files.pythonhosted.org/packages/d3/72/4deab446b579e8bd5dca91de98595c5d6bd6a17ce162abf5c5f2ce40d3d8/time_machine-3.2.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:027c1807efb74d0cd58ad16524dec94212fbe900115d70b0123399883657ac0f", size = 34792, upload-time = "2025-12-17T23:32:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/2c/39/439c6b587ddee76d533fe972289d0646e0a5520e14dc83d0a30aeb5565f7/time_machine-3.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92432610c05676edd5e6946a073c6f0c926923123ce7caee1018dc10782c713d", size = 36187, upload-time = "2025-12-17T23:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/4b/db/2da4368db15180989bab83746a857bde05ad16e78f326801c142bb747a06/time_machine-3.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c25586b62480eb77ef3d953fba273209478e1ef49654592cd6a52a68dfe56a67", size = 34855, upload-time = "2025-12-17T23:32:42.817Z" }, + { url = "https://files.pythonhosted.org/packages/88/84/120a431fee50bc4c241425bee4d3a4910df4923b7ab5f7dff1bf0c772f08/time_machine-3.2.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6bf3a2fa738d15e0b95d14469a0b8ea42635467408d8b490e263d5d45c9a177f", size = 33222, upload-time = "2025-12-17T23:32:43.94Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ea/89cfda82bb8c57ff91bb9a26751aa234d6d90e9b4d5ab0ad9dce0f9f0329/time_machine-3.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ce76b82276d7ad2a66cdc85dad4df19d1422b69183170a34e8fbc4c3f35502f7", size = 34270, upload-time = "2025-12-17T23:32:45.037Z" }, + { url = "https://files.pythonhosted.org/packages/8a/aa/235357da4f69a51a8d35fcbfcfa77cdc7dc24f62ae54025006570bda7e2d/time_machine-3.2.0-cp314-cp314-win32.whl", hash = "sha256:14d6778273c543441863dff712cd1d7803dee946b18de35921eb8df10714539d", size = 17544, upload-time = "2025-12-17T23:32:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/6c8405a7276be79693b792cff22ce41067ec05db26a7d02f2d5b06324434/time_machine-3.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbf821da96dbc80d349fa9e7c36e670b41d68a878d28c8850057992fed430eef", size = 18423, upload-time = "2025-12-17T23:32:47.468Z" }, + { url = "https://files.pythonhosted.org/packages/d9/03/a3cf419e20c35fc203c6e4fed48b5b667c1a2b4da456d9971e605f73ecef/time_machine-3.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:71c75d71f8e68abc8b669bca26ed2ddd558430a6c171e32b8620288565f18c0e", size = 17050, upload-time = "2025-12-17T23:32:48.91Z" }, + { url = "https://files.pythonhosted.org/packages/86/a1/142de946dc4393f910bf4564b5c3ba819906e1f49b06c9cb557519c849e4/time_machine-3.2.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4e374779021446fc2b5c29d80457ec9a3b1a5df043dc2aae07d7c1415d52323c", size = 19991, upload-time = "2025-12-17T23:32:49.933Z" }, + { url = "https://files.pythonhosted.org/packages/ee/62/7f17def6289901f94726921811a16b9adce46e666362c75d45730c60274f/time_machine-3.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:122310a6af9c36e9a636da32830e591e7923e8a07bdd0a43276c3a36c6821c90", size = 15707, upload-time = "2025-12-17T23:32:50.969Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d3/3502fb9bd3acb159c18844b26c43220201a0d4a622c0c853785d07699a92/time_machine-3.2.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba3eeb0f018cc362dd8128befa3426696a2e16dd223c3fb695fde184892d4d8c", size = 39207, upload-time = "2025-12-17T23:32:52.033Z" }, + { url = "https://files.pythonhosted.org/packages/5a/be/8b27f4aa296fda14a5a2ad7f588ddd450603c33415ab3f8e85b2f1a44678/time_machine-3.2.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:77d38ba664b381a7793f8786efc13b5004f0d5f672dae814430445b8202a67a6", size = 40764, upload-time = "2025-12-17T23:32:53.167Z" }, + { url = "https://files.pythonhosted.org/packages/42/cd/fe4c4e5c8ab6d48fab3624c32be9116fb120173a35fe67e482e5cf68b3d2/time_machine-3.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f09abeb8f03f044d72712207e0489a62098ad3ad16dac38927fcf80baca4d6a7", size = 43508, upload-time = "2025-12-17T23:32:54.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/28/5a3ba2fce85b97655a425d6bb20a441550acd2b304c96b2c19d3839f721a/time_machine-3.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6b28367ce4f73987a55e230e1d30a57a3af85da8eb1a140074eb6e8c7e6ef19f", size = 41712, upload-time = "2025-12-17T23:32:55.781Z" }, + { url = "https://files.pythonhosted.org/packages/81/58/e38084be7fdabb4835db68a3a47e58c34182d79fc35df1ecbe0db2c5359f/time_machine-3.2.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:903c7751c904581da9f7861c3015bed7cdc40047321291d3694a3cdc783bbca3", size = 38939, upload-time = "2025-12-17T23:32:56.867Z" }, + { url = "https://files.pythonhosted.org/packages/40/d0/ad3feb0a392ef4e0c08bc32024950373ddc0669002cbdcbb9f3bf0c2d114/time_machine-3.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:528217cad85ede5f85c8bc78b0341868d3c3cfefc6ecb5b622e1cacb6c73247b", size = 39837, upload-time = "2025-12-17T23:32:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/5b/9e/5f4b2ea63b267bd78f3245e76f5528836611b5f2d30b5e7300a722fe4428/time_machine-3.2.0-cp314-cp314t-win32.whl", hash = "sha256:75724762ffd517e7e80aaec1fad1ff5a7414bd84e2b3ee7a0bacfeb67c14926e", size = 18091, upload-time = "2025-12-17T23:32:59.403Z" }, + { url = "https://files.pythonhosted.org/packages/39/6f/456b1f4d2700ae02b19eba830f870596a4b89b74bac3b6c80666f1b108c5/time_machine-3.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2526abbd053c5bca898d1b3e7898eec34626b12206718d8c7ce88fd12c1c9c5c", size = 19208, upload-time = "2025-12-17T23:33:00.488Z" }, + { url = "https://files.pythonhosted.org/packages/2f/22/8063101427ecd3d2652aada4d21d0876b07a3dc789125bca2ee858fec3ed/time_machine-3.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7f2fb6784b414edbe2c0b558bfaab0c251955ba27edd62946cce4a01675a992c", size = 17359, upload-time = "2025-12-17T23:33:01.54Z" }, +] + +[[package]] +name = "types-defusedxml" +version = "0.7.0.20260504" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/d2/4553c8fa9cdebfe1e84b950cf790a4d2376a97fa016e43f7c65323fa6c7d/types_defusedxml-0.7.0.20260504.tar.gz", hash = "sha256:2ab2828a3f97111ba1c16cee273ad4124a831fc9198c41bf8368ff6ea48ad300", size = 10729, upload-time = "2026-05-04T05:22:50.192Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/2d/8a4b5ba1d732b8bc691f0efbb1c6f77cb5f6f473b2afe181d83d910773c5/types_defusedxml-0.7.0.20260504-py3-none-any.whl", hash = "sha256:a959e3a0a43b93e464bd625d91ec9193a2703ddc10443bdd627792485fae0b10", size = 13467, upload-time = "2026-05-04T05:22:49.319Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -947,9 +1079,9 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] From fbf04c8be2607a4ddc6670b1628729c143cd4d7c Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 7 May 2026 17:22:46 +0200 Subject: [PATCH 170/171] Fix a formatting issue --- src/aeonlib/salt/models/block_models.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/aeonlib/salt/models/block_models.py b/src/aeonlib/salt/models/block_models.py index 0fc7322..f3f9535 100644 --- a/src/aeonlib/salt/models/block_models.py +++ b/src/aeonlib/salt/models/block_models.py @@ -222,9 +222,11 @@ class Acquisition(BaseModel, validate_assignment=True): # type: ignore position_angle: Annotated[Angle | Literal["parallactic"] | None, LowerCaseValidator] reference_star: ReferenceStar | None = None do_not_flip_position_angle: bool | None = Field( - default_factory=lambda data: None - if isinstance(data["position_angle"], str) or data["position_angle"] is None - else False + default_factory=lambda data: ( + None + if isinstance(data["position_angle"], str) or data["position_angle"] is None + else False + ) ) include_focused_image: bool = False From 2d5452d0f8b7b858d766d40c296939d7b338591c Mon Sep 17 00:00:00 2001 From: Christian Hettlage Date: Thu, 7 May 2026 17:23:45 +0200 Subject: [PATCH 171/171] Add the dependency group for SALT when testing --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fc93d85..5524706 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,9 +4,9 @@ name: CI on: # Triggers the workflow on push or pull request events but only for the "main" branch push: - branches: ["main"] + branches: [ "main" ] pull_request: - branches: ["main"] + branches: [ "main" ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -44,7 +44,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install the project - run: uv sync --dev --extra eso --extra lt + run: uv sync --dev --extra eso --extra lt --extra salt - name: Run tests run: uv run pytest